@cat-factory/app 0.129.0 → 0.130.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/AgentFailureCard.vue +3 -1
- package/app/components/board/AgentStopButton.vue +3 -0
- package/app/components/board/BoardCanvas.vue +16 -3
- package/app/components/board/RecurringPipelineModal.vue +7 -1
- package/app/components/board/nodes/BlockNode.vue +47 -41
- package/app/components/brainstorm/BrainstormWindow.vue +25 -5
- package/app/components/clarity/ClarityReviewWindow.vue +17 -4
- package/app/components/docs/DocInterviewWindow.vue +5 -1
- package/app/components/followUp/FollowUpWindow.vue +15 -1
- package/app/components/forkDecision/ForkDecisionWindow.vue +7 -2
- package/app/components/gates/GateResultView.vue +7 -1
- package/app/components/humanTest/HumanTestWindow.vue +11 -5
- package/app/components/layout/BoardSwitcher.vue +21 -12
- package/app/components/layout/BoardToolbar.vue +8 -4
- package/app/components/layout/CommandBar.vue +137 -123
- package/app/components/layout/NotificationsInbox.vue +5 -1
- package/app/components/layout/SideBar.vue +157 -115
- package/app/components/layout/SpendWarningBanner.vue +3 -0
- package/app/components/panels/InspectorPanel.vue +23 -10
- package/app/components/panels/inspector/TaskExecution.vue +13 -4
- package/app/components/prReview/PrReviewWindow.vue +7 -3
- package/app/components/requirements/RequirementsReviewWindow.vue +33 -5
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +7 -3
- package/app/composables/useBlockDeletion.ts +7 -0
- package/app/composables/useBlockDrag.ts +5 -0
- package/app/composables/useFrameResize.ts +4 -0
- package/app/composables/useWorkspaceAccess.spec.ts +102 -0
- package/app/composables/useWorkspaceAccess.ts +84 -0
- package/app/stores/workspace.spec.ts +19 -0
- package/app/stores/workspace.ts +27 -7
- package/app/types/domain.ts +6 -0
- package/i18n/locales/de.json +5 -0
- package/i18n/locales/en.json +8 -0
- package/i18n/locales/es.json +5 -0
- package/i18n/locales/fr.json +5 -0
- package/i18n/locales/he.json +5 -0
- package/i18n/locales/it.json +5 -0
- package/i18n/locales/ja.json +5 -0
- package/i18n/locales/pl.json +5 -0
- package/i18n/locales/tr.json +5 -0
- package/i18n/locales/uk.json +5 -0
- package/package.json +1 -1
|
@@ -23,11 +23,30 @@ const accounts = useAccountsStore()
|
|
|
23
23
|
const auth = useAuthStore()
|
|
24
24
|
const providerConnections = useProviderConnectionsStore()
|
|
25
25
|
const ui = useUiStore()
|
|
26
|
+
const access = useWorkspaceAccess()
|
|
26
27
|
|
|
27
28
|
// The operator dashboard (deployment-level run health) is sensitive cross-workspace data,
|
|
28
29
|
// so it's shown only to an admin of the active account (matching the backend admin gate).
|
|
29
30
|
const isAccountAdmin = computed(() => accounts.activeAccount?.roles?.includes('admin') ?? false)
|
|
30
31
|
|
|
32
|
+
// Workspace-RBAC nav gating: each management destination is shown only when the caller
|
|
33
|
+
// holds the permission its writes require (the SPA mirror of the admin-tier controller
|
|
34
|
+
// middleware — a member/viewer who'd only get a 403 never sees the entry). Board authoring
|
|
35
|
+
// (create/repos) needs `board.write`; connections/infra/sandbox/bootstrap need
|
|
36
|
+
// `integrations.manage`; workspace + model configuration and the fragment library need
|
|
37
|
+
// `settings.manage`. Kaizen and account settings are reads / account-scoped and stay.
|
|
38
|
+
// Absent access (dev-open) ⇒ every `can*` is true, so nothing is hidden.
|
|
39
|
+
const showCreate = computed(() => access.canWriteBoard.value)
|
|
40
|
+
const showAddFromRepo = computed(() => github.available && access.canWriteBoard.value)
|
|
41
|
+
const showBootstrap = computed(() => access.canManageIntegrations.value)
|
|
42
|
+
const showRepositories = computed(() => showAddFromRepo.value || showBootstrap.value)
|
|
43
|
+
const showIntegrationsHub = computed(() => access.canManageIntegrations.value)
|
|
44
|
+
const showSandbox = computed(() => access.canManageIntegrations.value)
|
|
45
|
+
// The Configuration section carries workspace/model settings (`settings.manage`) AND the
|
|
46
|
+
// account-scoped account-settings / operator-dashboard entries, so it shows when the caller
|
|
47
|
+
// can manage settings OR accounts are enabled (the account entries have their own gates).
|
|
48
|
+
const showConfiguration = computed(() => access.canManageSettings.value || accounts.enabled)
|
|
49
|
+
|
|
31
50
|
// The Infrastructure menu (agent-container execution + test environments) shows whenever the
|
|
32
51
|
// deployment reports its infrastructure capability — every facade populates `auth.infrastructure`
|
|
33
52
|
// (it drives the execution-backend selector), so there is always an execution + test-env backend
|
|
@@ -36,12 +55,18 @@ const isAccountAdmin = computed(() => accounts.activeAccount?.roles?.includes('a
|
|
|
36
55
|
// (somehow) omits the descriptor.
|
|
37
56
|
const showInfrastructure = computed(
|
|
38
57
|
() =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
// Provisioning/managing infrastructure is `integrations.manage`, so a member/viewer
|
|
59
|
+
// never sees the section (they'd only 403 on the writes inside it).
|
|
60
|
+
access.canManageIntegrations.value &&
|
|
61
|
+
(auth.infrastructure != null ||
|
|
62
|
+
auth.localMode?.enabled === true ||
|
|
63
|
+
providerConnections.isAvailable('runner-pool') ||
|
|
64
|
+
providerConnections.isAvailable('environment')),
|
|
43
65
|
)
|
|
44
66
|
|
|
67
|
+
// The prompt-fragment library is a `settings.manage` authoring surface.
|
|
68
|
+
const showWorkspaceContext = computed(() => library.available && access.canManageSettings.value)
|
|
69
|
+
|
|
45
70
|
// `isCompact` (< lg) is the breakpoint at which the navbar is an off-canvas drawer;
|
|
46
71
|
// above it the aside is static and the drawer flag is inert.
|
|
47
72
|
const { isCompact } = useViewport()
|
|
@@ -166,57 +191,65 @@ watch(
|
|
|
166
191
|
<UKbd value="⌘K" />
|
|
167
192
|
</button>
|
|
168
193
|
|
|
169
|
-
<
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
<template v-if="showCreate">
|
|
195
|
+
<USeparator />
|
|
196
|
+
<section>
|
|
197
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
198
|
+
{{ t('nav.create') }}
|
|
199
|
+
</h2>
|
|
200
|
+
<div class="space-y-1.5">
|
|
201
|
+
<UButton
|
|
202
|
+
block
|
|
203
|
+
color="primary"
|
|
204
|
+
variant="soft"
|
|
205
|
+
size="sm"
|
|
206
|
+
icon="i-lucide-workflow"
|
|
207
|
+
class="justify-start"
|
|
208
|
+
data-testid="nav-build-pipeline"
|
|
209
|
+
@click="ui.openBuilder()"
|
|
210
|
+
>
|
|
211
|
+
{{ t('nav.buildPipeline') }}
|
|
212
|
+
</UButton>
|
|
213
|
+
</div>
|
|
214
|
+
</section>
|
|
215
|
+
</template>
|
|
189
216
|
|
|
190
|
-
<
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
<template v-if="showRepositories">
|
|
218
|
+
<USeparator />
|
|
219
|
+
<section>
|
|
220
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
221
|
+
{{ t('nav.repositories') }}
|
|
222
|
+
</h2>
|
|
223
|
+
<div class="space-y-1.5">
|
|
224
|
+
<UButton
|
|
225
|
+
v-if="showAddFromRepo"
|
|
226
|
+
block
|
|
227
|
+
color="primary"
|
|
228
|
+
variant="soft"
|
|
229
|
+
size="sm"
|
|
230
|
+
icon="i-lucide-folder-git-2"
|
|
231
|
+
class="justify-start"
|
|
232
|
+
data-testid="nav-add-from-repo"
|
|
233
|
+
@click="ui.openAddService()"
|
|
234
|
+
>
|
|
235
|
+
{{ t('nav.addFromRepo') }}
|
|
236
|
+
</UButton>
|
|
237
|
+
<UButton
|
|
238
|
+
v-if="showBootstrap"
|
|
239
|
+
block
|
|
240
|
+
color="primary"
|
|
241
|
+
variant="soft"
|
|
242
|
+
size="sm"
|
|
243
|
+
icon="i-lucide-git-branch-plus"
|
|
244
|
+
class="justify-start"
|
|
245
|
+
data-testid="nav-bootstrap-repo"
|
|
246
|
+
@click="ui.openBootstrap()"
|
|
247
|
+
>
|
|
248
|
+
{{ t('nav.bootstrapRepo') }}
|
|
249
|
+
</UButton>
|
|
250
|
+
</div>
|
|
251
|
+
</section>
|
|
252
|
+
</template>
|
|
220
253
|
|
|
221
254
|
<USeparator />
|
|
222
255
|
|
|
@@ -229,12 +262,14 @@ watch(
|
|
|
229
262
|
this single button — the hub modal lists them grouped (source control,
|
|
230
263
|
communication, documents, trackers, observability, model providers). -->
|
|
231
264
|
<UButton
|
|
265
|
+
v-if="showIntegrationsHub"
|
|
232
266
|
block
|
|
233
267
|
color="primary"
|
|
234
268
|
variant="soft"
|
|
235
269
|
size="sm"
|
|
236
270
|
icon="i-lucide-blocks"
|
|
237
271
|
class="justify-start"
|
|
272
|
+
data-testid="nav-integrations"
|
|
238
273
|
@click="ui.openIntegrations()"
|
|
239
274
|
>
|
|
240
275
|
{{ t('nav.integrations') }}
|
|
@@ -242,6 +277,7 @@ watch(
|
|
|
242
277
|
<!-- The Sandbox: try prompt versions/models against graded fixtures, off to the
|
|
243
278
|
side of the board. Opens the on-demand testing window. -->
|
|
244
279
|
<UButton
|
|
280
|
+
v-if="showSandbox"
|
|
245
281
|
block
|
|
246
282
|
color="primary"
|
|
247
283
|
variant="soft"
|
|
@@ -252,7 +288,8 @@ watch(
|
|
|
252
288
|
>
|
|
253
289
|
{{ t('nav.sandbox') }}
|
|
254
290
|
</UButton>
|
|
255
|
-
<!-- The Kaizen screen: grading history + verified prompt/agent/model combos.
|
|
291
|
+
<!-- The Kaizen screen: grading history + verified prompt/agent/model combos. A
|
|
292
|
+
read surface (grading history), so it stays visible to every resolved role. -->
|
|
256
293
|
<UButton
|
|
257
294
|
block
|
|
258
295
|
color="primary"
|
|
@@ -307,7 +344,7 @@ watch(
|
|
|
307
344
|
</section>
|
|
308
345
|
</template>
|
|
309
346
|
|
|
310
|
-
<template v-if="
|
|
347
|
+
<template v-if="showWorkspaceContext">
|
|
311
348
|
<USeparator />
|
|
312
349
|
<section>
|
|
313
350
|
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
@@ -327,66 +364,71 @@ watch(
|
|
|
327
364
|
</section>
|
|
328
365
|
</template>
|
|
329
366
|
|
|
330
|
-
<
|
|
331
|
-
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
367
|
+
<template v-if="showConfiguration">
|
|
368
|
+
<USeparator />
|
|
369
|
+
<section>
|
|
370
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
371
|
+
{{ t('nav.configuration') }}
|
|
372
|
+
</h2>
|
|
373
|
+
<div class="space-y-1.5">
|
|
374
|
+
<!-- Merge thresholds, issue writeback and default service best practices are
|
|
375
|
+
now tabs inside Workspace settings. `settings.manage` — hidden for members/viewers. -->
|
|
376
|
+
<UButton
|
|
377
|
+
v-if="access.canManageSettings.value"
|
|
378
|
+
block
|
|
379
|
+
color="primary"
|
|
380
|
+
variant="soft"
|
|
381
|
+
size="sm"
|
|
382
|
+
icon="i-lucide-sliders-horizontal"
|
|
383
|
+
class="justify-start"
|
|
384
|
+
data-testid="nav-workspace-settings"
|
|
385
|
+
@click="ui.openWorkspaceSettings()"
|
|
386
|
+
>
|
|
387
|
+
{{ t('nav.workspaceSettings') }}
|
|
388
|
+
</UButton>
|
|
389
|
+
<UButton
|
|
390
|
+
v-if="access.canManageSettings.value"
|
|
391
|
+
block
|
|
392
|
+
color="primary"
|
|
393
|
+
variant="soft"
|
|
394
|
+
size="sm"
|
|
395
|
+
icon="i-lucide-cpu"
|
|
396
|
+
class="justify-start"
|
|
397
|
+
@click="ui.openModelConfig()"
|
|
398
|
+
>
|
|
399
|
+
{{ t('nav.modelConfiguration') }}
|
|
400
|
+
</UButton>
|
|
401
|
+
<!-- Account & team: members + roles, invitations, email sender, account API keys.
|
|
361
402
|
Shown once accounts (auth) are enabled. -->
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
403
|
+
<UButton
|
|
404
|
+
v-if="accounts.enabled"
|
|
405
|
+
block
|
|
406
|
+
color="primary"
|
|
407
|
+
variant="soft"
|
|
408
|
+
size="sm"
|
|
409
|
+
icon="i-lucide-users"
|
|
410
|
+
class="justify-start"
|
|
411
|
+
@click="ui.openAccountSettings()"
|
|
412
|
+
>
|
|
413
|
+
{{ t('nav.accountSettings') }}
|
|
414
|
+
</UButton>
|
|
415
|
+
<!-- Platform observability: deployment-level run health. Admin-only, like the backend gate. -->
|
|
416
|
+
<UButton
|
|
417
|
+
v-if="accounts.enabled && isAccountAdmin"
|
|
418
|
+
block
|
|
419
|
+
color="primary"
|
|
420
|
+
variant="soft"
|
|
421
|
+
size="sm"
|
|
422
|
+
icon="i-lucide-gauge"
|
|
423
|
+
class="justify-start"
|
|
424
|
+
data-testid="nav-operator-dashboard"
|
|
425
|
+
@click="ui.openOperatorDashboard()"
|
|
426
|
+
>
|
|
427
|
+
{{ t('nav.operatorDashboard') }}
|
|
428
|
+
</UButton>
|
|
429
|
+
</div>
|
|
430
|
+
</section>
|
|
431
|
+
</template>
|
|
390
432
|
</div>
|
|
391
433
|
|
|
392
434
|
<div class="mt-auto space-y-2">
|
|
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|
|
3
3
|
|
|
4
4
|
const { t, n } = useI18n()
|
|
5
5
|
const workspace = useWorkspaceStore()
|
|
6
|
+
const access = useWorkspaceAccess()
|
|
6
7
|
|
|
7
8
|
const spend = computed(() => workspace.spend)
|
|
8
9
|
/** Show the large warning only once the budget has been reached. */
|
|
@@ -80,6 +81,8 @@ async function resume() {
|
|
|
80
81
|
variant="solid"
|
|
81
82
|
icon="i-lucide-play"
|
|
82
83
|
:loading="resuming"
|
|
84
|
+
:disabled="!access.canExecuteRuns.value"
|
|
85
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
83
86
|
@click="resume"
|
|
84
87
|
>
|
|
85
88
|
{{ t('layout.spendWarningBanner.resume') }}
|
|
@@ -35,6 +35,7 @@ const agentRuns = useAgentRunsStore()
|
|
|
35
35
|
const github = useGitHubStore()
|
|
36
36
|
const recurring = useRecurringPipelinesStore()
|
|
37
37
|
const requirements = useRequirementsStore()
|
|
38
|
+
const access = useWorkspaceAccess()
|
|
38
39
|
const { t } = useI18n()
|
|
39
40
|
|
|
40
41
|
// When the selected task block backs a recurring pipeline, the inspector shows the
|
|
@@ -105,15 +106,20 @@ const runnable = computed(() => (block.value ? board.isRunnable(block.value.id)
|
|
|
105
106
|
const unmetDepTitles = computed(() =>
|
|
106
107
|
block.value && isTask.value ? board.unmetDeps(block.value.id).map((b) => b.title) : [],
|
|
107
108
|
)
|
|
108
|
-
const runBlockedReason = computed(() =>
|
|
109
|
-
|
|
109
|
+
const runBlockedReason = computed(() => {
|
|
110
|
+
// A read-only viewer can inspect a task but never start/re-run it — surface WHY on the
|
|
111
|
+
// locked trigger, exactly like an unmet-dependency lock (the backend rejects it anyway).
|
|
112
|
+
if (!access.canExecuteRuns.value) return t('access.noRunExecute')
|
|
113
|
+
return unmetDepTitles.value.length
|
|
110
114
|
? t(
|
|
111
115
|
'panels.inspector.runBlocked',
|
|
112
116
|
{ count: unmetDepTitles.value.length, names: unmetDepTitles.value.join(', ') },
|
|
113
117
|
unmetDepTitles.value.length,
|
|
114
118
|
)
|
|
115
|
-
: null
|
|
116
|
-
)
|
|
119
|
+
: null
|
|
120
|
+
})
|
|
121
|
+
// The Run trigger is enabled only when the task is runnable AND the caller may execute runs.
|
|
122
|
+
const canRun = computed(() => runnable.value && access.canExecuteRuns.value)
|
|
117
123
|
|
|
118
124
|
// The delete control names what it removes, so selecting a task and deleting it
|
|
119
125
|
// reads as "Delete task" rather than ambiguously removing the whole service.
|
|
@@ -564,15 +570,16 @@ const showOriginalDescription = ref(false)
|
|
|
564
570
|
|
|
565
571
|
<!-- actions -->
|
|
566
572
|
<div class="flex items-center gap-2">
|
|
567
|
-
<UDropdownMenu v-if="isTask" :items="runMenu">
|
|
573
|
+
<UDropdownMenu v-if="isTask" :items="runMenu" :disabled="!canRun">
|
|
568
574
|
<UButton
|
|
569
|
-
:color="
|
|
575
|
+
:color="canRun ? 'primary' : 'neutral'"
|
|
570
576
|
variant="soft"
|
|
571
577
|
size="sm"
|
|
572
|
-
:icon="
|
|
578
|
+
:icon="canRun ? 'i-lucide-play' : 'i-lucide-lock'"
|
|
573
579
|
trailing-icon="i-lucide-chevron-down"
|
|
574
|
-
:disabled="!
|
|
580
|
+
:disabled="!canRun"
|
|
575
581
|
:title="runBlockedReason ?? undefined"
|
|
582
|
+
data-testid="run-start"
|
|
576
583
|
>
|
|
577
584
|
{{ instance ? t('panels.inspector.reRun') : t('panels.inspector.run') }}
|
|
578
585
|
</UButton>
|
|
@@ -595,7 +602,12 @@ const showOriginalDescription = ref(false)
|
|
|
595
602
|
icon="i-lucide-archive"
|
|
596
603
|
:class="isServiceFrame ? 'ms-auto' : ''"
|
|
597
604
|
data-testid="inspector-archive"
|
|
598
|
-
:
|
|
605
|
+
:disabled="!access.canWriteBoard.value"
|
|
606
|
+
:title="
|
|
607
|
+
access.canWriteBoard.value
|
|
608
|
+
? t('panels.inspector.archiveService')
|
|
609
|
+
: t('access.noBoardWrite')
|
|
610
|
+
"
|
|
599
611
|
@click="archive"
|
|
600
612
|
>
|
|
601
613
|
{{ t('panels.inspector.archiveService') }}
|
|
@@ -607,7 +619,8 @@ const showOriginalDescription = ref(false)
|
|
|
607
619
|
icon="i-lucide-trash-2"
|
|
608
620
|
:class="isServiceFrame ? '' : 'ms-auto'"
|
|
609
621
|
data-testid="inspector-delete"
|
|
610
|
-
:
|
|
622
|
+
:disabled="!access.canWriteBoard.value"
|
|
623
|
+
:title="access.canWriteBoard.value ? deleteLabel : t('access.noBoardWrite')"
|
|
611
624
|
@click="remove"
|
|
612
625
|
>
|
|
613
626
|
{{ deleteLabel }}
|
|
@@ -21,6 +21,7 @@ const agentRuns = useAgentRunsStore()
|
|
|
21
21
|
const ui = useUiStore()
|
|
22
22
|
const models = useModelsStore()
|
|
23
23
|
const reviews = useReviewStage()
|
|
24
|
+
const access = useWorkspaceAccess()
|
|
24
25
|
const { t, te } = useI18n()
|
|
25
26
|
const { confirm } = useConfirm()
|
|
26
27
|
|
|
@@ -232,8 +233,12 @@ async function mergePr() {
|
|
|
232
233
|
variant="ghost"
|
|
233
234
|
size="xs"
|
|
234
235
|
:loading="stopping"
|
|
235
|
-
:disabled="resetting"
|
|
236
|
-
:title="
|
|
236
|
+
:disabled="resetting || !access.canExecuteRuns.value"
|
|
237
|
+
:title="
|
|
238
|
+
access.canExecuteRuns.value
|
|
239
|
+
? t('inspector.execution.stopTooltip')
|
|
240
|
+
: t('access.noRunExecute')
|
|
241
|
+
"
|
|
237
242
|
data-testid="run-stop"
|
|
238
243
|
@click="stopRun"
|
|
239
244
|
>
|
|
@@ -246,8 +251,12 @@ async function mergePr() {
|
|
|
246
251
|
variant="ghost"
|
|
247
252
|
size="xs"
|
|
248
253
|
:loading="resetting"
|
|
249
|
-
:disabled="stopping"
|
|
250
|
-
:title="
|
|
254
|
+
:disabled="stopping || !access.canExecuteRuns.value"
|
|
255
|
+
:title="
|
|
256
|
+
access.canExecuteRuns.value
|
|
257
|
+
? t('inspector.execution.resetTooltip')
|
|
258
|
+
: t('access.noRunExecute')
|
|
259
|
+
"
|
|
251
260
|
data-testid="run-reset"
|
|
252
261
|
@click="resetRun"
|
|
253
262
|
>
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
const execution = useExecutionStore()
|
|
23
23
|
const board = useBoardStore()
|
|
24
24
|
const prReview = usePrReviewStore()
|
|
25
|
+
const access = useWorkspaceAccess()
|
|
25
26
|
|
|
26
27
|
const { t } = useI18n()
|
|
27
28
|
|
|
@@ -305,7 +306,8 @@ async function onResolve(action: PrReviewResolution): Promise<void> {
|
|
|
305
306
|
<UButton
|
|
306
307
|
color="neutral"
|
|
307
308
|
variant="ghost"
|
|
308
|
-
:disabled="!canResolve"
|
|
309
|
+
:disabled="!canResolve || !access.canExecuteRuns.value"
|
|
310
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
309
311
|
data-testid="pr-review-finish"
|
|
310
312
|
@click="onResolve('finish')"
|
|
311
313
|
>
|
|
@@ -314,7 +316,8 @@ async function onResolve(action: PrReviewResolution): Promise<void> {
|
|
|
314
316
|
<UButton
|
|
315
317
|
color="neutral"
|
|
316
318
|
variant="soft"
|
|
317
|
-
:disabled="!canResolve || !hasSelection"
|
|
319
|
+
:disabled="!canResolve || !hasSelection || !access.canExecuteRuns.value"
|
|
320
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
318
321
|
data-testid="pr-review-post"
|
|
319
322
|
@click="onResolve('post')"
|
|
320
323
|
>
|
|
@@ -323,7 +326,8 @@ async function onResolve(action: PrReviewResolution): Promise<void> {
|
|
|
323
326
|
<UButton
|
|
324
327
|
color="primary"
|
|
325
328
|
:loading="prReview.resolving"
|
|
326
|
-
:disabled="!canResolve || !hasSelection"
|
|
329
|
+
:disabled="!canResolve || !hasSelection || !access.canExecuteRuns.value"
|
|
330
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
327
331
|
data-testid="pr-review-fix"
|
|
328
332
|
@click="onResolve('fix')"
|
|
329
333
|
>
|
|
@@ -25,6 +25,7 @@ const requirements = useRequirementsStore()
|
|
|
25
25
|
const models = useModelsStore()
|
|
26
26
|
const toast = useToast()
|
|
27
27
|
const { t } = useI18n()
|
|
28
|
+
const access = useWorkspaceAccess()
|
|
28
29
|
|
|
29
30
|
// Draft replies, keyed by item id, so editing one item doesn't disturb others.
|
|
30
31
|
const drafts = ref<Record<string, string>>({})
|
|
@@ -837,7 +838,12 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
837
838
|
variant="soft"
|
|
838
839
|
size="xs"
|
|
839
840
|
icon="i-lucide-check"
|
|
840
|
-
:disabled="frozen"
|
|
841
|
+
:disabled="frozen || !access.canExecuteRuns.value"
|
|
842
|
+
:title="
|
|
843
|
+
access.canExecuteRuns.value
|
|
844
|
+
? undefined
|
|
845
|
+
: t('access.noRunExecute')
|
|
846
|
+
"
|
|
841
847
|
@click="acceptRecommendation(rec)"
|
|
842
848
|
>
|
|
843
849
|
{{ t('requirements.accept') }}
|
|
@@ -847,7 +853,12 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
847
853
|
variant="ghost"
|
|
848
854
|
size="xs"
|
|
849
855
|
icon="i-lucide-x"
|
|
850
|
-
:disabled="frozen"
|
|
856
|
+
:disabled="frozen || !access.canExecuteRuns.value"
|
|
857
|
+
:title="
|
|
858
|
+
access.canExecuteRuns.value
|
|
859
|
+
? undefined
|
|
860
|
+
: t('access.noRunExecute')
|
|
861
|
+
"
|
|
851
862
|
@click="rejectRecommendation(rec)"
|
|
852
863
|
>
|
|
853
864
|
{{ t('requirements.reject') }}
|
|
@@ -869,7 +880,16 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
869
880
|
size="xs"
|
|
870
881
|
icon="i-lucide-rotate-cw"
|
|
871
882
|
:loading="recommending"
|
|
872
|
-
:disabled="
|
|
883
|
+
:disabled="
|
|
884
|
+
!(reRequestNotes[rec.id] ?? '').trim() ||
|
|
885
|
+
frozen ||
|
|
886
|
+
!access.canExecuteRuns.value
|
|
887
|
+
"
|
|
888
|
+
:title="
|
|
889
|
+
access.canExecuteRuns.value
|
|
890
|
+
? undefined
|
|
891
|
+
: t('access.noRunExecute')
|
|
892
|
+
"
|
|
873
893
|
@click="reRequestRecommendation(rec)"
|
|
874
894
|
>
|
|
875
895
|
{{ t('requirements.reRequest') }}
|
|
@@ -1025,6 +1045,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
1025
1045
|
block
|
|
1026
1046
|
icon="i-lucide-wand-2"
|
|
1027
1047
|
:loading="recommending"
|
|
1048
|
+
:disabled="!access.canExecuteRuns.value"
|
|
1049
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1028
1050
|
@click="requestRecommendations"
|
|
1029
1051
|
>
|
|
1030
1052
|
{{
|
|
@@ -1050,6 +1072,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
1050
1072
|
icon="i-lucide-arrow-right"
|
|
1051
1073
|
:ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
|
|
1052
1074
|
:loading="acting"
|
|
1075
|
+
:disabled="!access.canExecuteRuns.value"
|
|
1076
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1053
1077
|
@click="proceed"
|
|
1054
1078
|
>
|
|
1055
1079
|
{{ t('requirements.actions.proceedNothing') }}
|
|
@@ -1061,7 +1085,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
1061
1085
|
block
|
|
1062
1086
|
icon="i-lucide-wand-sparkles"
|
|
1063
1087
|
:loading="reworking"
|
|
1064
|
-
:disabled="!canIncorporate"
|
|
1088
|
+
:disabled="!canIncorporate || !access.canExecuteRuns.value"
|
|
1089
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1065
1090
|
@click="incorporate()"
|
|
1066
1091
|
>
|
|
1067
1092
|
{{ t('requirements.actions.incorporateAnswers') }}
|
|
@@ -1085,6 +1110,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
1085
1110
|
block
|
|
1086
1111
|
icon="i-lucide-sparkles"
|
|
1087
1112
|
:loading="busy"
|
|
1113
|
+
:disabled="!access.canExecuteRuns.value"
|
|
1114
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1088
1115
|
@click="reReview"
|
|
1089
1116
|
>
|
|
1090
1117
|
{{
|
|
@@ -1123,7 +1150,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
1123
1150
|
block
|
|
1124
1151
|
icon="i-lucide-wand-sparkles"
|
|
1125
1152
|
:loading="reworking"
|
|
1126
|
-
:disabled="!redoComment.trim()"
|
|
1153
|
+
:disabled="!redoComment.trim() || !access.canExecuteRuns.value"
|
|
1154
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1127
1155
|
@click="incorporate(redoComment.trim())"
|
|
1128
1156
|
>
|
|
1129
1157
|
{{ t('requirements.actions.redoWithDirection') }}
|
|
@@ -19,6 +19,7 @@ const board = useBoardStore()
|
|
|
19
19
|
const execution = useExecutionStore()
|
|
20
20
|
const visualConfirm = useVisualConfirmStore()
|
|
21
21
|
const { t } = useI18n()
|
|
22
|
+
const access = useWorkspaceAccess()
|
|
22
23
|
|
|
23
24
|
// Per-window blob cache; release the cached screenshot/reference object URLs when the window
|
|
24
25
|
// goes away, so the (potentially large) blob bytes don't linger for the rest of the session.
|
|
@@ -351,7 +352,8 @@ const canApprove = computed(
|
|
|
351
352
|
color="warning"
|
|
352
353
|
icon="i-lucide-wrench"
|
|
353
354
|
:loading="busy"
|
|
354
|
-
:disabled="busy || !hasFindings"
|
|
355
|
+
:disabled="busy || !hasFindings || !access.canExecuteRuns.value"
|
|
356
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
355
357
|
@click="submitFix"
|
|
356
358
|
>
|
|
357
359
|
{{ t('visualConfirm.requestFix.send') }}
|
|
@@ -430,7 +432,8 @@ const canApprove = computed(
|
|
|
430
432
|
color="neutral"
|
|
431
433
|
icon="i-lucide-refresh-cw"
|
|
432
434
|
:loading="busy"
|
|
433
|
-
:disabled="busy || !awaitingHuman"
|
|
435
|
+
:disabled="busy || !awaitingHuman || !access.canExecuteRuns.value"
|
|
436
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
434
437
|
@click="recapture"
|
|
435
438
|
>
|
|
436
439
|
{{ t('visualConfirm.recapture') }}
|
|
@@ -439,7 +442,8 @@ const canApprove = computed(
|
|
|
439
442
|
color="primary"
|
|
440
443
|
icon="i-lucide-circle-check"
|
|
441
444
|
:loading="busy"
|
|
442
|
-
:disabled="!canApprove"
|
|
445
|
+
:disabled="!canApprove || !access.canExecuteRuns.value"
|
|
446
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
443
447
|
@click="approve"
|
|
444
448
|
>
|
|
445
449
|
{{ t('visualConfirm.approve') }}
|