@cat-factory/app 0.309.0 → 0.310.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/README.md +11 -2
- package/app/components/assistant/AssistantModal.vue +5 -4
- package/app/components/auth/BackendMisconfiguredScreen.vue +4 -3
- package/app/components/auth/LoginScreen.vue +40 -26
- package/app/components/auth/ResetPasswordScreen.vue +9 -4
- package/app/components/auth/UserMenu.vue +4 -3
- package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +34 -23
- package/app/components/binaryOutput/StoredAssetView.vue +8 -6
- package/app/components/board/AddTaskModal.vue +16 -15
- package/app/components/board/AgentFailureCard.vue +12 -9
- package/app/components/board/AgentFailureHistory.vue +18 -9
- package/app/components/board/CreateInitiativeModal.vue +5 -4
- package/app/components/board/FailureDetail.vue +22 -12
- package/app/components/board/ReviewFrictionDialog.vue +4 -3
- package/app/components/board/nodes/BlockNode.vue +17 -15
- package/app/components/board/nodes/DecisionBadge.vue +4 -3
- package/app/components/board/nodes/FrameSwimlanes.vue +4 -3
- package/app/components/board/nodes/TaskCard.vue +12 -7
- package/app/components/board/nodes/TaskPipelineMini.vue +9 -7
- package/app/components/bootstrap/AdoptionReviewModal.vue +30 -21
- package/app/components/bootstrap/BootstrapModal.vue +3 -3
- package/app/components/brainstorm/BrainstormWindow.vue +5 -3
- package/app/components/bugFishing/BugFishingWindow.vue +49 -27
- package/app/components/clarity/ClarityReviewWindow.vue +5 -3
- package/app/components/common/CopyButton.vue +12 -11
- package/app/components/common/IconButton.vue +34 -9
- package/app/components/context/ContextAttachmentFields.vue +23 -12
- package/app/components/documents/ContextDocumentPicker.vue +12 -9
- package/app/components/documents/DocumentImportModal.vue +4 -3
- package/app/components/documents/DocumentOriginLink.vue +12 -9
- package/app/components/documents/DocumentSyncState.vue +3 -3
- package/app/components/documents/RepoContextDocPicker.vue +4 -3
- package/app/components/environments/EnvironmentStatusPanel.vue +4 -3
- package/app/components/followUp/FollowUpWindow.vue +9 -6
- package/app/components/forkDecision/ForkDecisionWindow.vue +71 -80
- package/app/components/foundational/FoundationalServiceCatalogList.vue +3 -2
- package/app/components/foundational/FoundationalServiceRegistry.vue +7 -6
- package/app/components/foundational/FoundationalServiceSources.vue +7 -6
- package/app/components/fragments/FragmentLibraryManager.vue +5 -4
- package/app/components/fragments/FragmentSelector.vue +12 -9
- package/app/components/gates/GateFailingCheckList.vue +4 -3
- package/app/components/gates/GateResultView.vue +17 -12
- package/app/components/github/AddServiceFromRepoModal.vue +11 -7
- package/app/components/github/GitHubOnboarding.vue +7 -2
- package/app/components/github/GitHubPanel.vue +9 -6
- package/app/components/github/RepoTreeBrowser.vue +15 -12
- package/app/components/humanTest/HumanTestWindow.vue +13 -9
- package/app/components/initiative/InitiativePlanReview.vue +28 -18
- package/app/components/initiative/InitiativeTrackerWindow.vue +171 -124
- package/app/components/judge/JudgeResultView.vue +5 -3
- package/app/components/kaizen/KaizenPanel.vue +67 -53
- package/app/components/layout/AccountTeamSettings.vue +9 -9
- package/app/components/layout/AppearanceSwitcher.vue +4 -3
- package/app/components/layout/BoardSwitcher.vue +10 -8
- package/app/components/layout/BoardToolbar.vue +5 -4
- package/app/components/layout/CommandBar.vue +4 -3
- package/app/components/layout/DefaultTestEnvBanner.vue +4 -3
- package/app/components/layout/IntegrationsHub.vue +8 -6
- package/app/components/layout/LanguageSwitcher.vue +4 -3
- package/app/components/layout/ModelProvidersHub.vue +4 -3
- package/app/components/layout/NotificationsInbox.vue +40 -30
- package/app/components/layout/PersonalSetupModal.vue +4 -3
- package/app/components/layout/RolePrompt.vue +4 -3
- package/app/components/layout/SideBar.vue +4 -3
- package/app/components/layout/TranslationWarningBanner.vue +4 -3
- package/app/components/layout/UiModeSwitcher.vue +8 -6
- package/app/components/layout/UiRoleSwitcher.vue +8 -6
- package/app/components/layout/WorkspaceMembersSettings.vue +2 -2
- package/app/components/media/ArtifactLightbox.vue +69 -39
- package/app/components/media/ImageCompare.vue +69 -74
- package/app/components/observability/OutcomeFilterChips.vue +5 -4
- package/app/components/observability/RunFailureSummary.vue +8 -6
- package/app/components/observability/StepModelActivity.vue +5 -3
- package/app/components/observability/ToolCallList.vue +4 -2
- package/app/components/outcome/OutcomeSummaryWindow.vue +5 -4
- package/app/components/palettes/AgentPalette.vue +18 -14
- package/app/components/panels/AgentStepDetail.vue +30 -19
- package/app/components/panels/ObservabilityPanel.vue +149 -114
- package/app/components/panels/OperatorDashboardPanel.vue +99 -91
- package/app/components/panels/ReportsPanel.vue +64 -61
- package/app/components/panels/ResultWindowShell.vue +148 -134
- package/app/components/panels/StepContainerStatus.vue +9 -9
- package/app/components/panels/StepDelegatedStatus.vue +11 -9
- package/app/components/panels/StepRestartControl.vue +3 -2
- package/app/components/panels/inspector/EpicChildren.vue +4 -3
- package/app/components/panels/inspector/FrontendConfig.vue +3 -2
- package/app/components/panels/inspector/InspectorSection.vue +22 -17
- package/app/components/panels/inspector/ServiceConnections.vue +3 -2
- package/app/components/panels/inspector/ServiceTestConfig.vue +5 -4
- package/app/components/panels/inspector/TaskExecution.vue +5 -4
- package/app/components/panels/inspector/TaskRunSettings.vue +10 -6
- package/app/components/pipeline/EstimateThresholdFields.vue +13 -10
- package/app/components/pipeline/GateConfigFields.vue +14 -18
- package/app/components/pipeline/PipelineBuilder.vue +90 -77
- package/app/components/pipeline/PipelinePicker.vue +8 -6
- package/app/components/pipeline/PipelineProgress.vue +26 -21
- package/app/components/prReview/PrReviewWindow.vue +46 -25
- package/app/components/providers/ApiKeysSection.vue +4 -3
- package/app/components/ralph/RalphLoopResultView.vue +4 -3
- package/app/components/requirements/RequirementsReviewWindow.vue +10 -6
- package/app/components/riskPolicy/RiskPolicyPicker.vue +8 -6
- package/app/components/sandbox/SandboxPanel.vue +73 -54
- package/app/components/settings/CloudflareHandlerSection.vue +33 -30
- package/app/components/settings/ConsensusGroupsSection.vue +40 -30
- package/app/components/settings/InfraHandlersConfigurator.vue +16 -13
- package/app/components/settings/InfrastructureBackendPicker.vue +17 -17
- package/app/components/settings/IssueTrackerPanel.vue +21 -6
- package/app/components/settings/LocalModelEndpointsPanel.vue +3 -2
- package/app/components/settings/ModelConfigurationPanel.vue +9 -8
- package/app/components/settings/OpenRouterCatalogPanel.vue +4 -3
- package/app/components/settings/ProviderPreferenceEditor.vue +5 -6
- package/app/components/settings/RiskPolicyEditorRow.vue +3 -2
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +10 -8
- package/app/components/skills/ReviewSkillQueue.vue +4 -3
- package/app/components/skills/SkillLibraryManager.vue +7 -6
- package/app/components/slack/SlackPanel.vue +9 -5
- package/app/components/spec/ServiceSpecWindow.vue +4 -3
- package/app/components/tasks/ContextIssuePicker.vue +12 -9
- package/app/components/tasks/TaskContextIssues.vue +4 -3
- package/app/components/testing/TestReportWindow.vue +14 -8
- package/app/components/theme/ThemeImportModal.vue +2 -2
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +62 -35
- package/i18n/locales/de.json +7 -3
- package/i18n/locales/en.json +7 -3
- package/i18n/locales/es.json +7 -3
- package/i18n/locales/fr.json +7 -3
- package/i18n/locales/he.json +7 -3
- package/i18n/locales/it.json +7 -3
- package/i18n/locales/ja.json +7 -3
- package/i18n/locales/pl.json +7 -3
- package/i18n/locales/tr.json +7 -3
- package/i18n/locales/uk.json +7 -3
- package/package.json +2 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, watch } from 'vue'
|
|
3
3
|
import { onKeyStroke } from '@vueuse/core'
|
|
4
|
+
import type { TableColumn } from '@nuxt/ui'
|
|
4
5
|
import type { PlatformObservabilityWindow } from '~/types/execution'
|
|
5
6
|
import { formatMs } from '~/utils/observability'
|
|
6
7
|
import { FAILURE_KIND_KEYS, isAgentFailureKind } from '~/utils/failureKinds'
|
|
7
8
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
9
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
8
10
|
|
|
9
11
|
// Deployment-level (platform-operator) observability dashboard: the aggregate health of the
|
|
10
12
|
// active account's runs — outcome totals + success rate, a time-bucketed outcome trend, the
|
|
@@ -68,6 +70,19 @@ function barPct(count: number, max: number): number {
|
|
|
68
70
|
// Share of a gate kind's runs the precheck satisfied outright, 0..1: the number the
|
|
69
71
|
// precheck-before-escalate design exists to move. Null (not 0) when nothing settled, because
|
|
70
72
|
// "no gates ran" is not "every gate needed a fixer".
|
|
73
|
+
// Column definitions rather than hand-written `<th>`s: the header typography is the table
|
|
74
|
+
// theme's, shared with every other table in the SPA.
|
|
75
|
+
// The count columns are flush right, so their headers are too.
|
|
76
|
+
const NUMERIC = { class: { th: 'text-end' } }
|
|
77
|
+
const gateColumns = computed<TableColumn<NonNullable<typeof view.value>['gates'][number]>[]>(() => [
|
|
78
|
+
{ id: 'gate', header: t('platformObservability.gates.gate') },
|
|
79
|
+
{ id: 'settled', header: t('platformObservability.gates.settled'), meta: NUMERIC },
|
|
80
|
+
{ id: 'cleanPasses', header: t('platformObservability.gates.cleanPasses'), meta: NUMERIC },
|
|
81
|
+
{ id: 'attempts', header: t('platformObservability.gates.attempts'), meta: NUMERIC },
|
|
82
|
+
{ id: 'helperFailures', header: t('platformObservability.gates.helperFailures'), meta: NUMERIC },
|
|
83
|
+
{ id: 'exhausted', header: t('platformObservability.gates.exhausted'), meta: NUMERIC },
|
|
84
|
+
])
|
|
85
|
+
|
|
71
86
|
function cleanRate(stat: { gates: number; cleanPasses: number }): number | null {
|
|
72
87
|
return stat.gates > 0 ? stat.cleanPasses / stat.gates : null
|
|
73
88
|
}
|
|
@@ -125,44 +140,44 @@ watch(
|
|
|
125
140
|
<p v-if="accountName" class="truncate text-xs text-dimmed">{{ accountName }}</p>
|
|
126
141
|
</div>
|
|
127
142
|
<div class="ms-auto flex items-center gap-1.5">
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
:
|
|
147
|
-
:
|
|
143
|
+
<UTabs
|
|
144
|
+
:model-value="platform.window"
|
|
145
|
+
:items="WINDOWS"
|
|
146
|
+
:content="false"
|
|
147
|
+
size="xs"
|
|
148
|
+
class="me-1"
|
|
149
|
+
@update:model-value="setWindow($event as PlatformObservabilityWindow)"
|
|
150
|
+
>
|
|
151
|
+
<!-- UTabs renders its own triggers and forwards nothing from an item, so this
|
|
152
|
+
slot is the one place a stable per-window selector can live. -->
|
|
153
|
+
<template #default="{ item }">
|
|
154
|
+
<span :data-testid="`operator-window-${item.value}`">{{ item.label }}</span>
|
|
155
|
+
</template>
|
|
156
|
+
</UTabs>
|
|
157
|
+
<IconButton
|
|
158
|
+
color="neutral"
|
|
159
|
+
variant="ghost"
|
|
160
|
+
icon="i-lucide-refresh-cw"
|
|
161
|
+
:label="t('platformObservability.refresh')"
|
|
162
|
+
:ui="{
|
|
163
|
+
base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
|
|
164
|
+
leadingIcon: loading ? 'h-4 w-4 animate-spin' : 'h-4 w-4',
|
|
165
|
+
}"
|
|
148
166
|
data-testid="operator-refresh"
|
|
149
167
|
@click="refresh"
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
:aria-label="t('platformObservability.close')"
|
|
168
|
+
/>
|
|
169
|
+
<IconButton
|
|
170
|
+
color="neutral"
|
|
171
|
+
variant="ghost"
|
|
172
|
+
icon="i-lucide-x"
|
|
173
|
+
:label="t('platformObservability.close')"
|
|
174
|
+
:ui="{
|
|
175
|
+
base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
|
|
176
|
+
leadingIcon: 'h-4 w-4',
|
|
177
|
+
}"
|
|
161
178
|
data-testid="operator-close"
|
|
162
179
|
@click="close"
|
|
163
|
-
|
|
164
|
-
<UIcon name="i-lucide-x" class="h-4 w-4" />
|
|
165
|
-
</button>
|
|
180
|
+
/>
|
|
166
181
|
</div>
|
|
167
182
|
</header>
|
|
168
183
|
|
|
@@ -172,12 +187,14 @@ watch(
|
|
|
172
187
|
class="mx-auto max-w-2xl rounded-lg border border-app-error-800/60 bg-app-error-950/40 p-4 text-sm text-app-error-200"
|
|
173
188
|
>
|
|
174
189
|
<p>{{ error }}</p>
|
|
175
|
-
<
|
|
190
|
+
<UButton
|
|
191
|
+
color="neutral"
|
|
192
|
+
variant="ghost"
|
|
176
193
|
class="mt-2 rounded-md border border-app-error-700 px-3 py-1 text-xs hover:bg-app-error-900/40"
|
|
177
194
|
@click="refresh"
|
|
178
195
|
>
|
|
179
196
|
{{ t('platformObservability.retry') }}
|
|
180
|
-
</
|
|
197
|
+
</UButton>
|
|
181
198
|
</div>
|
|
182
199
|
|
|
183
200
|
<div v-else-if="loading && !view" class="py-16 text-center text-sm text-muted">
|
|
@@ -327,62 +344,53 @@ watch(
|
|
|
327
344
|
<p v-if="!view.gates.length" class="py-4 text-center text-xs text-dimmed">
|
|
328
345
|
{{ t('platformObservability.gates.empty') }}
|
|
329
346
|
</p>
|
|
330
|
-
<
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
</
|
|
345
|
-
<th class="pb-2 pe-3 text-end font-medium">
|
|
346
|
-
{{ t('platformObservability.gates.helperFailures') }}
|
|
347
|
-
</th>
|
|
348
|
-
<th class="pb-2 text-end font-medium">
|
|
349
|
-
{{ t('platformObservability.gates.exhausted') }}
|
|
350
|
-
</th>
|
|
351
|
-
</tr>
|
|
352
|
-
</thead>
|
|
353
|
-
<tbody class="text-toned">
|
|
354
|
-
<tr
|
|
355
|
-
v-for="g in view.gates"
|
|
356
|
-
:key="g.gateKind"
|
|
357
|
-
class="border-t border-default/70"
|
|
347
|
+
<UTable
|
|
348
|
+
v-else
|
|
349
|
+
:data="view.gates"
|
|
350
|
+
:columns="gateColumns"
|
|
351
|
+
:ui="{
|
|
352
|
+
base: 'text-xs',
|
|
353
|
+
th: 'ps-0 pe-3 py-2',
|
|
354
|
+
td: 'ps-0 pe-3 py-2 text-xs text-toned whitespace-normal',
|
|
355
|
+
}"
|
|
356
|
+
data-testid="operator-gates"
|
|
357
|
+
>
|
|
358
|
+
<template #gate-cell="{ row }">
|
|
359
|
+
<span class="font-medium text-default">{{ row.original.gateKind }}</span>
|
|
360
|
+
<span v-if="row.original.helperKind" class="ms-1.5 text-dimmed"
|
|
361
|
+
>→ {{ row.original.helperKind }}</span
|
|
358
362
|
>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
<
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
363
|
+
</template>
|
|
364
|
+
<template #settled-cell="{ row }">
|
|
365
|
+
<span class="block text-end tabular-nums">{{ row.original.gates }}</span>
|
|
366
|
+
</template>
|
|
367
|
+
<template #cleanPasses-cell="{ row }">
|
|
368
|
+
<span class="block text-end tabular-nums">
|
|
369
|
+
<span class="text-app-success-400">{{ row.original.cleanPasses }}</span>
|
|
370
|
+
<span v-if="cleanRate(row.original) !== null" class="ms-1 text-dimmed"
|
|
371
|
+
>({{ n(cleanRate(row.original) ?? 0, 'percent') }})</span
|
|
372
|
+
>
|
|
373
|
+
</span>
|
|
374
|
+
</template>
|
|
375
|
+
<template #attempts-cell="{ row }">
|
|
376
|
+
<span class="block text-end tabular-nums">{{ row.original.attempts }}</span>
|
|
377
|
+
</template>
|
|
378
|
+
<template #helperFailures-cell="{ row }">
|
|
379
|
+
<span class="block text-end tabular-nums">
|
|
380
|
+
<span
|
|
381
|
+
:class="row.original.helperFailures > 0 ? 'text-app-warning-400' : ''"
|
|
382
|
+
>{{ row.original.helperFailures }}</span
|
|
383
|
+
>
|
|
384
|
+
</span>
|
|
385
|
+
</template>
|
|
386
|
+
<template #exhausted-cell="{ row }">
|
|
387
|
+
<span class="block text-end tabular-nums">
|
|
388
|
+
<span :class="row.original.exhausted > 0 ? 'text-app-error-400' : ''">{{
|
|
389
|
+
row.original.exhausted
|
|
390
|
+
}}</span>
|
|
391
|
+
</span>
|
|
392
|
+
</template>
|
|
393
|
+
</UTable>
|
|
386
394
|
<p class="mt-3 text-2xs leading-relaxed text-dimmed">
|
|
387
395
|
{{ t('platformObservability.gates.hint') }}
|
|
388
396
|
</p>
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from './ReportsPanel.logic'
|
|
21
21
|
import ReportsSpendBreakdown from '~/components/panels/ReportsSpendBreakdown.vue'
|
|
22
22
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
23
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
23
24
|
|
|
24
25
|
// Reports: cross-cutting usage analytics for the active account — where the spend and the
|
|
25
26
|
// work actually go. Spend per model and agent kind, spend + run activity per workspace /
|
|
@@ -137,6 +138,14 @@ const maxRuns = computed(() => maxOf(activityByDimension.value, (row) => row.run
|
|
|
137
138
|
|
|
138
139
|
/** Boards the filter offers — the active account's, since the report is account-scoped. */
|
|
139
140
|
const boards = computed(() => workspace.accountWorkspaces)
|
|
141
|
+
// "Every board" is a NAMED sentinel, not an empty string: an empty string is what a Select
|
|
142
|
+
// reserves for "nothing selected", and Reka throws on an item that carries one. The store holds
|
|
143
|
+
// the same state as null, so the two are mapped at the boundary below.
|
|
144
|
+
const ALL_BOARDS = 'all'
|
|
145
|
+
const boardFilterItems = computed(() => [
|
|
146
|
+
{ label: t('reports.filter.allBoards'), value: ALL_BOARDS },
|
|
147
|
+
...boards.value.map((board) => ({ label: board.name, value: board.id })),
|
|
148
|
+
])
|
|
140
149
|
|
|
141
150
|
function trendTooltip(point: { start: number; meteredCost: number; subscriptionCost: number }) {
|
|
142
151
|
return `${d(new Date(point.start), 'short')} · ${t('reports.legend.metered')} ${money(point.meteredCost)} · ${t('reports.legend.subscription')} ${money(point.subscriptionCost)}`
|
|
@@ -206,55 +215,52 @@ watch(
|
|
|
206
215
|
</div>
|
|
207
216
|
<!-- Filters in ONE row above the charts: window, then board scope. -->
|
|
208
217
|
<div class="ms-auto flex flex-wrap items-center gap-1.5">
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
:
|
|
218
|
+
<USelect
|
|
219
|
+
:model-value="reports.workspaceFilter ?? ALL_BOARDS"
|
|
220
|
+
:items="boardFilterItems"
|
|
221
|
+
size="xs"
|
|
212
222
|
:aria-label="t('reports.filter.board')"
|
|
213
223
|
data-testid="reports-board-filter"
|
|
214
|
-
@
|
|
215
|
-
reports.setWorkspaceFilter(
|
|
224
|
+
@update:model-value="
|
|
225
|
+
reports.setWorkspaceFilter($event === ALL_BOARDS ? null : String($event))
|
|
216
226
|
"
|
|
227
|
+
/>
|
|
228
|
+
<UTabs
|
|
229
|
+
:model-value="reports.window"
|
|
230
|
+
:items="WINDOWS"
|
|
231
|
+
:content="false"
|
|
232
|
+
size="xs"
|
|
233
|
+
class="me-1"
|
|
234
|
+
@update:model-value="reports.setWindow($event as ReportWindow)"
|
|
217
235
|
>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
:data-testid="`reports-window-${opt.value}`"
|
|
234
|
-
@click="reports.setWindow(opt.value)"
|
|
235
|
-
>
|
|
236
|
-
{{ opt.label }}
|
|
237
|
-
</button>
|
|
238
|
-
</div>
|
|
239
|
-
<button
|
|
240
|
-
class="rounded-lg border border-default p-1.5 text-muted transition hover:text-default"
|
|
241
|
-
:aria-label="t('reports.refresh')"
|
|
242
|
-
:title="t('reports.refresh')"
|
|
236
|
+
<!-- UTabs renders its own triggers and forwards nothing from an item, so this
|
|
237
|
+
slot is the one place a stable per-window selector can live. -->
|
|
238
|
+
<template #default="{ item }">
|
|
239
|
+
<span :data-testid="`reports-window-${item.value}`">{{ item.label }}</span>
|
|
240
|
+
</template>
|
|
241
|
+
</UTabs>
|
|
242
|
+
<IconButton
|
|
243
|
+
color="neutral"
|
|
244
|
+
variant="ghost"
|
|
245
|
+
icon="i-lucide-refresh-cw"
|
|
246
|
+
:label="t('reports.refresh')"
|
|
247
|
+
:ui="{
|
|
248
|
+
base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
|
|
249
|
+
leadingIcon: loading ? 'h-4 w-4 animate-spin' : 'h-4 w-4',
|
|
250
|
+
}"
|
|
243
251
|
@click="refresh"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
/>
|
|
253
|
+
<IconButton
|
|
254
|
+
color="neutral"
|
|
255
|
+
variant="ghost"
|
|
256
|
+
icon="i-lucide-x"
|
|
257
|
+
:label="t('common.close')"
|
|
258
|
+
:ui="{
|
|
259
|
+
base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
|
|
260
|
+
leadingIcon: 'h-4 w-4',
|
|
261
|
+
}"
|
|
254
262
|
@click="close"
|
|
255
|
-
|
|
256
|
-
<UIcon name="i-lucide-x" class="h-4 w-4" />
|
|
257
|
-
</button>
|
|
263
|
+
/>
|
|
258
264
|
</div>
|
|
259
265
|
</header>
|
|
260
266
|
|
|
@@ -265,12 +271,14 @@ watch(
|
|
|
265
271
|
>
|
|
266
272
|
<p>{{ t('reports.error') }}</p>
|
|
267
273
|
<p v-if="error" class="mt-1 text-xs text-app-error-300/80">{{ error }}</p>
|
|
268
|
-
<
|
|
274
|
+
<UButton
|
|
275
|
+
color="neutral"
|
|
276
|
+
variant="ghost"
|
|
269
277
|
class="mt-2 rounded-md border border-app-error-700 px-3 py-1 text-xs hover:bg-app-error-900/40"
|
|
270
278
|
@click="refresh"
|
|
271
279
|
>
|
|
272
280
|
{{ t('reports.retry') }}
|
|
273
|
-
</
|
|
281
|
+
</UButton>
|
|
274
282
|
</div>
|
|
275
283
|
|
|
276
284
|
<div v-else-if="loading && !view" class="py-16 text-center text-sm text-muted">
|
|
@@ -466,22 +474,17 @@ watch(
|
|
|
466
474
|
<SectionLabel as="h2">
|
|
467
475
|
{{ t('reports.breakdown.title') }}
|
|
468
476
|
</SectionLabel>
|
|
469
|
-
<
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
@click="dimension = opt.value"
|
|
481
|
-
>
|
|
482
|
-
{{ opt.label }}
|
|
483
|
-
</button>
|
|
484
|
-
</div>
|
|
477
|
+
<UTabs
|
|
478
|
+
:model-value="dimension"
|
|
479
|
+
:items="DIMENSIONS"
|
|
480
|
+
:content="false"
|
|
481
|
+
size="xs"
|
|
482
|
+
@update:model-value="dimension = $event as ReportActivityDimension"
|
|
483
|
+
>
|
|
484
|
+
<template #default="{ item }">
|
|
485
|
+
<span :data-testid="`reports-dimension-${item.value}`">{{ item.label }}</span>
|
|
486
|
+
</template>
|
|
487
|
+
</UTabs>
|
|
485
488
|
</div>
|
|
486
489
|
<div class="grid gap-6 md:grid-cols-2">
|
|
487
490
|
<div>
|