mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 12:42:49 +00:00
Move frontend to Tailwind CSS 4 and jscpd 5 to drop braces (#2443)
## Why Since 3 October, npm advisory GHSA-vfj7-8cjw-p6xm (`braces` <= 3.0.3, no patched release) gives seven high findings in `frontend-modern`. The required frontend dependency audit fails closed on any pull request that changes `package.json` or `package-lock.json`. Every finding was dev-only (`npm audit --omit=dev` was already clean), through two chains: - `tailwindcss` 3 -> `chokidar` 3, `fast-glob`, `micromatch` -> `braces` - `jscpd` 4 -> `@jscpd/finder` -> `fast-glob` -> `micromatch` -> `braces` Every `fast-glob` and `micromatch` release depends on `braces`, so `overrides` cannot fix this. Suppressions, allowlists and severity thresholds are not used. ## What changes - `tailwindcss` 4.3.3 through `@tailwindcss/vite`. `tailwind.config.js`, `postcss.config.js`, `autoprefixer` and the direct `postcss` dependency are removed (Vite still bundles its own PostCSS). - `jscpd` 5.4.0, which has no runtime dependencies. This supersedes Dependabot #2278 (jscpd 5.3.2). - `npm audit --json` over the full graph: **0 findings**. `braces`, `micromatch`, `fast-glob` and `chokidar` are gone from the lockfile. - `dependencySecurity.test.ts` now rejects any locked `braces` copy. It fails on main's lockfile and passes here. The `autoprefixer` floor is dropped because the package is gone. - Templates were migrated with `@tailwindcss/upgrade`. I reverted 39 false positives where it rewrote prop values and prose (`shape: 'rounded'`, `appearance: 'outline'`, test names containing "blur" or "rounded"). I finished the template-literal classes it skipped. - `src/index.css` was migrated by hand, because the tool turned every `@layer` rule into an `@utility`. `src/tailwind-palette.css` is new. - `shared-template-registry.json` retired patterns use the new class names, so the drift audit still matches the source. - `check-assistant-identity.mjs` and `managed-local-backend.mjs` no longer read the deleted config files. ## How v3 rendering is kept Most of v4's defaults differ from v3's. Each item below was found by measuring, not by reading the upgrade guide: | v4 change | Kept as v3 by | |---|---| | OKLCH palette | v3 hex palette pinned, inlined so `/NN` modifiers compile to static colours (no runtime `color-mix`) | | `--font-sans` without `system-ui` (4.3) | v3 font stacks pinned | | Unitless line heights (`text-[11px]` inside `text-xs` went 16px -> 14.67px) | v3 absolute line heights pinned | | Native cascade layers: any utility beats any component rule | former `@layer components` and unlayered rules placed in the utilities layer before and after the generated utilities, matching v3's flat cascade (for example the container-query `hidden sm:table-cell` overrides) | | `space-*`/`divide-*` moved to `:where(> :not(:last-child))` | v3 selectors restored via `@utility`, v4 rules neutralised | | hover only on hover-capable pointers | `@custom-variant hover (&:hover)` | | `dark:` specificity | `@custom-variant dark (&:is(.dark *))` (same as v3) | | border default `currentColor`, ring 1px `currentColor`, placeholder, button cursor, table cells' 1px padding | compat rules in the base layer | | Opacity modifier multiplies a translucent colour (v3 replaced the alpha) | the 900/950 shades Pulse overrode with 25% alpha are opaque now; the 1,203 callers that relied on the implicit alpha say `/25` | | `bg-surface/40`-style modifiers on `var()` colours now render (v3 silently emitted nothing) | the 103 never-rendered tokens are removed | | `pb-safe-or-14 xl:pb-0`: v3 variants came after the custom rule | single-class helpers are now `@utility`, so variants win again | | Utility sort changed from lexical to numeric | see below | ### Sort-order flips v3 sorted `min-h-11` before `min-h-6`, so some caller overrides lost to a shared primitive's base class without anyone noticing. v4 sorts numerically. A runtime scan of 82 states and a static scan of every `Button`/`ButtonLink`/`ActionIconButton`/`CopyValueButton`/`TableCell`/`TableHead`/composed-class call site found these: - **Touch targets** (`min-h-11 min-w-11` on phone; guardrail tests assert them): these now render at their authored 44px. The Machines row action uses `-my-2`, so the 44x44 target fits the 32px row and the table is pixel-identical. The Roles edit/delete buttons, the mobile threshold cancel button and Patrol's Copy ID button get the 44px phone target they were written with. - **Cosmetic overrides v3 never applied** (`justify-start` on Reporting, SSO, drawer-attention, Snooze and Patrol buttons; `align-top` in `DetailSectionTable`; `sm:text-[11px]` on storage headers): these are removed, so the UI renders exactly as it does today. Example: the Snooze menu options stay centred. - **Overrides v3 did apply but v4 would not**: these use the important suffix (`min-h-9!`, `justify-between!`, `flex-nowrap!`). ## Verification - Private mock stack with main (v3) and this branch (v4) served side by side from one backend. A Playwright rig compared every element's computed style and a full-page pixel diff. - **64 states** (16 routes x light/dark x 1440 / 390 with touch emulation): 59 pixel-identical. The other five are mock drift (alert counters, timestamps, tab-strip scroll timing) that was identical on rerun, plus 12 antialiased edge pixels on vSphere at 390. - Hover (desktop and touch), keyboard focus and row expansion: identical. Focus keeps the same 2px blue ring. The invisible transparent outline is now `outline-style: none`, and `outline-hidden` keeps it in forced-colours mode. - Docs page: pixel-identical. Prose colours differ by at most 2/255, because `@tailwindcss/typography` reads v4's palette. - `format:check`, `lint` (eslint, theme, copy, canonical-platform, form-label, table-row, external-domain and planning-doc audits), `type-check`: pass. `make frontend` and `check:bundlesize`: pass. CSS is 31.12 kB gzip against a 31.59 kB baseline. - `vitest`: 21,758 passed. The only failures are the six `agentInstallCommand` lifecycle tests, which need GNU `stat` and fail on macOS on main too. - `npm run lint:cpd` with jscpd 5: exit 0, 39 clones (0.50%) against the 3% threshold. jscpd 4 reported 130 clones because it also re-scanned every TSX file as JavaScript despite the config's `format` list. jscpd 5 honours that list. - `scripts/npm-audit-retry.sh all`: no vulnerabilities. ## Browser support (product consequence) Tailwind CSS 4 is supported on **Safari 16.4+, Chrome 111+ and Firefox 128+**; below that it is untested. What this build actually needs is less than that. The production CSS has media range syntax and nesting lowered, and palette opacity modifiers folded to static colours, so its hard requirement is cascade layers: Safari 15.4, Chrome 99, Firefox 97. Pulse's platform tables already need container queries (Safari 16, Chrome 105, Firefox 110) to lay out correctly. So a browser that renders Pulse correctly today should still do so, but anything between those floors and Tailwind's tested floor (Safari 16.0-16.3, Firefox 110-127, including the Firefox 115 ESR that Windows 7/8.1 users are stuck on) is untested. I found no issue reports from users on such browsers. Supersedes #2278 (jscpd 5.3.2).
This commit is contained in:
@@ -2814,6 +2814,33 @@ promotion and rollback boundary unchanged.
|
||||
`brace-expansion` floors to 1.1.21 and 5.0.12 and the reviewed `dompurify`
|
||||
floor to 3.4.16, so a later downgrade is rejected.
|
||||
|
||||
### braces removed from the frontend dependency graph (3 Oct 2026)
|
||||
|
||||
`GHSA-vfj7-8cjw-p6xm` affects every `braces` release (up to 3.0.3) and has no
|
||||
patched version, so the required frontend dependency audit failed with seven
|
||||
high findings on every pull request that changes the dependency graph. All of
|
||||
them were dev-only: Tailwind CSS 3 reached `braces` through `chokidar`,
|
||||
`fast-glob` and `micromatch`, and `jscpd` 4 through `@jscpd/finder` and
|
||||
`fast-glob`. Every `fast-glob` and `micromatch` release depends on `braces`, so
|
||||
an override cannot close it. `frontend-modern` therefore moves to Tailwind CSS
|
||||
4.3.3 through `@tailwindcss/vite` (dropping `postcss.config.js`,
|
||||
`tailwind.config.js`, `autoprefixer` and the direct `postcss` dependency) and to
|
||||
`jscpd` 5.4.0, which has no runtime dependencies; `npm audit` over the full graph
|
||||
then reports no findings. `npm audit --omit=dev` was already clean, so no shipped
|
||||
bundle code changes for security reasons, and the installer, artifact, signing,
|
||||
promotion and rollback boundary is unchanged. The stylesheet keeps the v3
|
||||
rendering: it pins the v3 palette, font stacks and line heights, keeps the v3
|
||||
cascade order, and restores the v3 `space-*`, `divide-*`, hover, border, ring,
|
||||
placeholder and button-cursor behaviour. Tailwind CSS 4 is tested on Safari
|
||||
16.4, Chrome 111 and Firefox 128 and later. The production stylesheet itself
|
||||
needs cascade layers (Safari 15.4, Chrome 99, Firefox 97), because the build
|
||||
lowers media range syntax and nesting and folds palette opacity modifiers to
|
||||
static colors; that is below the container-query floor (Safari 16, Chrome 105,
|
||||
Firefox 110) the platform tables already required.
|
||||
`frontend-modern/src/security/__tests__/dependencySecurity.test.ts` now rejects
|
||||
any locked `braces` copy, so the advisory cannot return through a later
|
||||
dependency, and drops the `autoprefixer` floor because the package is gone.
|
||||
|
||||
### Reviewed @types/node 26.6.2 refresh
|
||||
|
||||
The 2026-09-23 `npm-minor-patch` group (Dependabot #2189) advances the
|
||||
|
||||
@@ -6,7 +6,7 @@ This document outlines the standard UI primitives, tokens, and components that c
|
||||
|
||||
## Core Principles
|
||||
1. **Never hardcode hex values** or use static `gray` / `white` / `slate` labels for structural layout colors.
|
||||
2. Use the **Semantic Tokens**. These resolve dynamically inside `tailwind.config.js` via `index.css` CSS-variables to flawlessly support light/dark transitions without needing a literal `dark:` prefix in the layout classes.
|
||||
2. Use the **Semantic Tokens**. These are declared as Tailwind `@theme` colors in `index.css` and resolve through its CSS variables to flawlessly support light/dark transitions without needing a literal `dark:` prefix in the layout classes.
|
||||
|
||||
## Enforcement
|
||||
- `npm run lint` is a hard gate for design-system regressions in `src/components` and `src/pages`.
|
||||
|
||||
@@ -1,100 +1,802 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "0e90236d3bb901dc84d4d07e70d4d2a09d5e6705",
|
||||
"verified_at": "2026-10-02T23:55:48Z",
|
||||
"base_sha": "74fd189fd03098b7ed94d806b15f570722048670",
|
||||
"verified_at": "2026-10-03T14:24:00Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/shared/Table.tsx"
|
||||
"frontend-modern/src/AppLayout.tsx",
|
||||
"frontend-modern/src/api/patrol.ts",
|
||||
"frontend-modern/src/components/AI/AICostDashboard.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/ApprovalCard.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/AssistantCommandHelpDialog.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/ChatMessages.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/MentionAutocomplete.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/MessageItem.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/ModelSelector.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/QuestionCard.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/ThinkingBlock.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx",
|
||||
"frontend-modern/src/components/AI/Chat/index.tsx",
|
||||
"frontend-modern/src/components/AI/FindingsPanel.tsx",
|
||||
"frontend-modern/src/components/AI/aiChatUtils.ts",
|
||||
"frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx",
|
||||
"frontend-modern/src/components/Alerts/AlertResourceTableMobile.tsx",
|
||||
"frontend-modern/src/components/Alerts/AlertResourceTableRow.tsx",
|
||||
"frontend-modern/src/components/Alerts/BulkEditDialog.tsx",
|
||||
"frontend-modern/src/components/Alerts/EmailProviderSelect.tsx",
|
||||
"frontend-modern/src/components/Alerts/IncidentAssistantHandoffButton.tsx",
|
||||
"frontend-modern/src/components/Alerts/IncidentTimelineEventCard.tsx",
|
||||
"frontend-modern/src/components/Alerts/InvestigateAlertButton.tsx",
|
||||
"frontend-modern/src/components/Alerts/ResourceTable.tsx",
|
||||
"frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTable.tsx",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableDockerIgnoredPrefixesSection.tsx",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableDockerServiceGapSection.tsx",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableDockerUpdateAlertsSection.tsx",
|
||||
"frontend-modern/src/components/Alerts/WebhookConfigForm.tsx",
|
||||
"frontend-modern/src/components/Alerts/WebhookConfigList.tsx",
|
||||
"frontend-modern/src/components/AppBootstrapStatus.tsx",
|
||||
"frontend-modern/src/components/Brand/PulseBrandMark.tsx",
|
||||
"frontend-modern/src/components/Discovery/AvailabilityProposalCard.tsx",
|
||||
"frontend-modern/src/components/Discovery/DiscoveryTab.tsx",
|
||||
"frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx",
|
||||
"frontend-modern/src/components/ErrorBoundary.tsx",
|
||||
"frontend-modern/src/components/GitHubStarBanner.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/AvailabilityProbeStatusCard.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ClusterDeployBanner.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/MaintenanceVerificationSection.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ReportMergeModal.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceActionHistory.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailSummary.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceInvestigationContextTables.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourceSourceBadgeCell.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts",
|
||||
"frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx",
|
||||
"frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx",
|
||||
"frontend-modern/src/components/Login.tsx",
|
||||
"frontend-modern/src/components/OrgSwitcher.tsx",
|
||||
"frontend-modern/src/components/SecurityWarning.tsx",
|
||||
"frontend-modern/src/components/Settings/AIChatMaintenanceSection.tsx",
|
||||
"frontend-modern/src/components/Settings/AIModelSelectionSection.tsx",
|
||||
"frontend-modern/src/components/Settings/AIProviderConfigurationSection.tsx",
|
||||
"frontend-modern/src/components/Settings/AIRuntimeControlsSection.tsx",
|
||||
"frontend-modern/src/components/Settings/AISettings.tsx",
|
||||
"frontend-modern/src/components/Settings/AISettingsStatusAndActions.tsx",
|
||||
"frontend-modern/src/components/Settings/APITokenManager.tsx",
|
||||
"frontend-modern/src/components/Settings/APITokenManagerDialogs.tsx",
|
||||
"frontend-modern/src/components/Settings/AgentIntegrationsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/AgentProfilesPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/AuditLogPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/AuditWebhookPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/AvailabilitySettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/BackupTransferDialogs.tsx",
|
||||
"frontend-modern/src/components/Settings/BillingAdminOrganizationsTable.tsx",
|
||||
"frontend-modern/src/components/Settings/BillingAdminPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/BrandingSettingsCard.tsx",
|
||||
"frontend-modern/src/components/Settings/ChangePasswordModal.tsx",
|
||||
"frontend-modern/src/components/Settings/CommercialBillingSections.tsx",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx",
|
||||
"frontend-modern/src/components/Settings/DataHandlingPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/DiagnosticsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/DiagnosticsResultsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/DiscoverySettingsForm.tsx",
|
||||
"frontend-modern/src/components/Settings/DockerRuntimeSettingsCard.tsx",
|
||||
"frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/GuestDockerDiscoverySettingsCard.tsx",
|
||||
"frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx",
|
||||
"frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx",
|
||||
"frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx",
|
||||
"frontend-modern/src/components/Settings/InfrastructureSourcePicker.tsx",
|
||||
"frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx",
|
||||
"frontend-modern/src/components/Settings/NetworkBoundarySettingsSection.tsx",
|
||||
"frontend-modern/src/components/Settings/NodeModalClusterMembersSection.tsx",
|
||||
"frontend-modern/src/components/Settings/NodeModalMonitoringSection.tsx",
|
||||
"frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx",
|
||||
"frontend-modern/src/components/Settings/NodeModalStatusFooter.tsx",
|
||||
"frontend-modern/src/components/Settings/OrganizationAccessManagementSection.tsx",
|
||||
"frontend-modern/src/components/Settings/OrganizationAccessMembersSection.tsx",
|
||||
"frontend-modern/src/components/Settings/OrganizationOverviewDetailsSection.tsx",
|
||||
"frontend-modern/src/components/Settings/OrganizationSharingCreateSection.tsx",
|
||||
"frontend-modern/src/components/Settings/ProLicensePlanSection.tsx",
|
||||
"frontend-modern/src/components/Settings/QuickSecuritySetup.tsx",
|
||||
"frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/RelaySettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/ReportingPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/ResourcePicker.tsx",
|
||||
"frontend-modern/src/components/Settings/RolesEditorDialog.tsx",
|
||||
"frontend-modern/src/components/Settings/SSOProvidersPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/SecurityAuthPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/SelfHostedCommercialRecoverySection.tsx",
|
||||
"frontend-modern/src/components/Settings/SettingsPageShell.tsx",
|
||||
"frontend-modern/src/components/Settings/SuggestProfileModal.tsx",
|
||||
"frontend-modern/src/components/Settings/SystemLogsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/UpdateInstallGuide.tsx",
|
||||
"frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/UserAssignmentsDialog.tsx",
|
||||
"frontend-modern/src/components/Settings/UserAssignmentsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/availabilitySettingsModel.ts",
|
||||
"frontend-modern/src/components/Settings/connectionsTableModel.ts",
|
||||
"frontend-modern/src/components/Settings/useConnectionsLedger.ts",
|
||||
"frontend-modern/src/components/Settings/useProLicensePanelState.ts",
|
||||
"frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx",
|
||||
"frontend-modern/src/components/SetupWizard/steps/SecurityStep.tsx",
|
||||
"frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx",
|
||||
"frontend-modern/src/components/Storage/EnhancedStorageBar.tsx",
|
||||
"frontend-modern/src/components/Storage/StorageDetailKeyValueRow.tsx",
|
||||
"frontend-modern/src/components/Storage/StoragePoolDetail.tsx",
|
||||
"frontend-modern/src/components/Storage/StoragePoolsTable.tsx",
|
||||
"frontend-modern/src/components/Toast/Toast.tsx",
|
||||
"frontend-modern/src/components/TokenRevealDialog.tsx",
|
||||
"frontend-modern/src/components/UpdateBanner.tsx",
|
||||
"frontend-modern/src/components/UpdateConfirmationModal.tsx",
|
||||
"frontend-modern/src/components/WhatsNewCard.tsx",
|
||||
"frontend-modern/src/components/Workloads/AvailabilityProbeSuggestionCard.tsx",
|
||||
"frontend-modern/src/components/Workloads/DiskList.tsx",
|
||||
"frontend-modern/src/components/Workloads/DrawerDiskListCard.tsx",
|
||||
"frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx",
|
||||
"frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx",
|
||||
"frontend-modern/src/components/Workloads/GuestPhysicalDisks.tsx",
|
||||
"frontend-modern/src/components/Workloads/GuestRow.tsx",
|
||||
"frontend-modern/src/components/Workloads/GuestRowCells.tsx",
|
||||
"frontend-modern/src/components/Workloads/MetricMiniSparkline.tsx",
|
||||
"frontend-modern/src/components/Workloads/StackedDiskBar.tsx",
|
||||
"frontend-modern/src/components/Workloads/StackedMemoryBar.tsx",
|
||||
"frontend-modern/src/components/Workloads/ThresholdSlider.tsx",
|
||||
"frontend-modern/src/components/Workloads/WorkloadPanel.tsx",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsFilter.tsx",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsSurface.tsx",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsTable.tsx",
|
||||
"frontend-modern/src/components/Workloads/useGuestRowState.ts",
|
||||
"frontend-modern/src/components/patrol/ApprovalBanner.tsx",
|
||||
"frontend-modern/src/components/patrol/ApprovalSection.tsx",
|
||||
"frontend-modern/src/components/patrol/InvestigationMessages.tsx",
|
||||
"frontend-modern/src/components/patrol/InvestigationSection.tsx",
|
||||
"frontend-modern/src/components/patrol/RemediationStatus.tsx",
|
||||
"frontend-modern/src/components/patrol/RunHistoryEntry.tsx",
|
||||
"frontend-modern/src/components/patrol/RunHistoryPanel.tsx",
|
||||
"frontend-modern/src/components/patrol/RunToolCallTrace.tsx",
|
||||
"frontend-modern/src/components/shared/AIModelPicker.tsx",
|
||||
"frontend-modern/src/components/shared/CalloutCard.tsx",
|
||||
"frontend-modern/src/components/shared/Card.tsx",
|
||||
"frontend-modern/src/components/shared/ColumnPicker.tsx",
|
||||
"frontend-modern/src/components/shared/CommandPaletteModal.tsx",
|
||||
"frontend-modern/src/components/shared/ContainerUpdateBadge.tsx",
|
||||
"frontend-modern/src/components/shared/CopyableCodeRow.tsx",
|
||||
"frontend-modern/src/components/shared/DetailSectionTable.tsx",
|
||||
"frontend-modern/src/components/shared/Dialog.tsx",
|
||||
"frontend-modern/src/components/shared/DiscoveryReadinessBadge.tsx",
|
||||
"frontend-modern/src/components/shared/DrawerAttentionSection.tsx",
|
||||
"frontend-modern/src/components/shared/ExternalTextLink.tsx",
|
||||
"frontend-modern/src/components/shared/FeatureGateSection.tsx",
|
||||
"frontend-modern/src/components/shared/FilterBar/AddFilterMenu.tsx",
|
||||
"frontend-modern/src/components/shared/FilterBar/FilterBar.tsx",
|
||||
"frontend-modern/src/components/shared/FilterBar/FilterChip.tsx",
|
||||
"frontend-modern/src/components/shared/FilterBar/ViewOptionsDisclosure.tsx",
|
||||
"frontend-modern/src/components/shared/FilterButtonGroup.tsx",
|
||||
"frontend-modern/src/components/shared/FilterToolbar.tsx",
|
||||
"frontend-modern/src/components/shared/Form.ts",
|
||||
"frontend-modern/src/components/shared/HelpIcon.tsx",
|
||||
"frontend-modern/src/components/shared/HistoryChart.tsx",
|
||||
"frontend-modern/src/components/shared/HistoryChartHeader.tsx",
|
||||
"frontend-modern/src/components/shared/HistoryChartOverlay.tsx",
|
||||
"frontend-modern/src/components/shared/HistoryChartTooltip.tsx",
|
||||
"frontend-modern/src/components/shared/InfoCardFrame.tsx",
|
||||
"frontend-modern/src/components/shared/KeyboardShortcutsModal.tsx",
|
||||
"frontend-modern/src/components/shared/MetadataBadge.tsx",
|
||||
"frontend-modern/src/components/shared/MobileNavBar.tsx",
|
||||
"frontend-modern/src/components/shared/NodeGroupHeader.tsx",
|
||||
"frontend-modern/src/components/shared/ObjectDrawerHeader.tsx",
|
||||
"frontend-modern/src/components/shared/ProgressBar.tsx",
|
||||
"frontend-modern/src/components/shared/PulseDataGrid.tsx",
|
||||
"frontend-modern/src/components/shared/SearchField.tsx",
|
||||
"frontend-modern/src/components/shared/SearchTipsPopover.tsx",
|
||||
"frontend-modern/src/components/shared/SettingsLoadingSkeleton.tsx",
|
||||
"frontend-modern/src/components/shared/SkipToContentLink.tsx",
|
||||
"frontend-modern/src/components/shared/StatusDot.tsx",
|
||||
"frontend-modern/src/components/shared/StatusIndicatorBadge.tsx",
|
||||
"frontend-modern/src/components/shared/Subtabs.tsx",
|
||||
"frontend-modern/src/components/shared/TableCardHeader.tsx",
|
||||
"frontend-modern/src/components/shared/TagBadges.tsx",
|
||||
"frontend-modern/src/components/shared/TechnicalDetailsDisclosure.tsx",
|
||||
"frontend-modern/src/components/shared/TlsVerificationWarningBanner.tsx",
|
||||
"frontend-modern/src/components/shared/Tooltip.tsx",
|
||||
"frontend-modern/src/components/shared/TooltipPortal.tsx",
|
||||
"frontend-modern/src/components/shared/WebInterfaceLink.tsx",
|
||||
"frontend-modern/src/components/shared/WebInterfaceUrlField.tsx",
|
||||
"frontend-modern/src/components/shared/WorkloadTypeBadge.tsx",
|
||||
"frontend-modern/src/components/shared/buttonModel.ts",
|
||||
"frontend-modern/src/components/shared/cards/NetworkInterfacesCard.tsx",
|
||||
"frontend-modern/src/components/shared/cards/RaidCard.tsx",
|
||||
"frontend-modern/src/components/shared/containerUpdateBadgeModel.ts",
|
||||
"frontend-modern/src/components/shared/dialogModel.ts",
|
||||
"frontend-modern/src/components/shared/filterButtonGroupModel.ts",
|
||||
"frontend-modern/src/components/shared/groupedTableRowPresentation.ts",
|
||||
"frontend-modern/src/components/shared/mobileNavBarModel.ts",
|
||||
"frontend-modern/src/components/shared/searchInputEnhancementsModel.ts",
|
||||
"frontend-modern/src/components/shared/searchTipsPopoverModel.ts",
|
||||
"frontend-modern/src/components/shared/selectablePillModel.ts",
|
||||
"frontend-modern/src/components/shared/selectionCardGroupModel.ts",
|
||||
"frontend-modern/src/components/shared/sourcePlatformBadges.ts",
|
||||
"frontend-modern/src/components/shared/statusBadgeModel.ts",
|
||||
"frontend-modern/src/components/shared/summaryInteractionA11y.ts",
|
||||
"frontend-modern/src/components/shared/tagInputModel.ts",
|
||||
"frontend-modern/src/components/shared/toggleModel.ts",
|
||||
"frontend-modern/src/features/actions/ActionDecisionPacket.tsx",
|
||||
"frontend-modern/src/features/actions/ActionReviewDialog.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertAppriseDestinationsSection.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertDeadManDestinationSection.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertDeliveryHealthCard.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertDeliveryPausedCard.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertDestinationsLoadErrorCard.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertDestinationsLoadingState.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertEscalationSection.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertHistoryAdministrationCard.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertHistoryFrequencyCard.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertIncidentSynthesisSummary.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertOverviewAlertCard.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx",
|
||||
"frontend-modern/src/features/alerts/AlertSnoozeAction.tsx",
|
||||
"frontend-modern/src/features/alerts/ResourceMonitoringPolicyAction.tsx",
|
||||
"frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx",
|
||||
"frontend-modern/src/features/docker/DockerConfigsTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerContainerLifecycleControls.tsx",
|
||||
"frontend-modern/src/features/docker/DockerHostDrawerOverview.tsx",
|
||||
"frontend-modern/src/features/docker/DockerHostsTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerNativeTableShared.tsx",
|
||||
"frontend-modern/src/features/docker/DockerNetworksTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerSecretsTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerServicesTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerStorageUsageTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerTasksTable.tsx",
|
||||
"frontend-modern/src/features/docker/DockerVolumesTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesEventsTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolAutopilotAcknowledgementDialog.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceWorkspace.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolObjectivesPanel.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolRecentWorkPanel.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolWeeklyDigestCard.tsx",
|
||||
"frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx",
|
||||
"frontend-modern/src/features/platformPage/PlatformWindowedRows.tsx",
|
||||
"frontend-modern/src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"frontend-modern/src/features/proxmox/BackupActivityChart.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupsCoverageStrip.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCephTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxMailGatewayTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxReplicationTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx",
|
||||
"frontend-modern/src/features/proxmox/proxmoxHostTableModel.ts",
|
||||
"frontend-modern/src/features/standalone/AgentsMachinesTable.tsx",
|
||||
"frontend-modern/src/features/standalone/AvailabilityFleetView.tsx",
|
||||
"frontend-modern/src/features/standalone/StandalonePageSurface.tsx",
|
||||
"frontend-modern/src/features/storageBackups/detailPresentation.ts",
|
||||
"frontend-modern/src/features/storageBackups/diskPresentation.ts",
|
||||
"frontend-modern/src/features/storageBackups/groupPresentation.ts",
|
||||
"frontend-modern/src/features/storageBackups/storageDomain.ts",
|
||||
"frontend-modern/src/features/storageBackups/storageFilterPresentation.ts",
|
||||
"frontend-modern/src/features/storageBackups/storagePagePresentation.ts",
|
||||
"frontend-modern/src/features/storageBackups/storageRowAlertPresentation.ts",
|
||||
"frontend-modern/src/features/storageBackups/zfsPresentation.ts",
|
||||
"frontend-modern/src/features/truenas/TrueNASAppsTable.tsx",
|
||||
"frontend-modern/src/features/truenas/TrueNASServicesTable.tsx",
|
||||
"frontend-modern/src/features/truenas/TrueNASSystemsTable.tsx",
|
||||
"frontend-modern/src/features/vmware/VmwarePageSurface.tsx",
|
||||
"frontend-modern/src/features/vmware/VsphereHostsTable.tsx",
|
||||
"frontend-modern/src/index.css",
|
||||
"frontend-modern/src/pages/Actions.tsx",
|
||||
"frontend-modern/src/pages/Alerts.tsx",
|
||||
"frontend-modern/src/pages/Docs.tsx",
|
||||
"frontend-modern/src/pages/PricingHandoff.tsx",
|
||||
"frontend-modern/src/tailwind-palette.css",
|
||||
"frontend-modern/src/utils/actionAuditPresentation.ts",
|
||||
"frontend-modern/src/utils/agentCapabilityPresentation.ts",
|
||||
"frontend-modern/src/utils/agentProfileSuggestionPresentation.ts",
|
||||
"frontend-modern/src/utils/aiControlLevelPresentation.ts",
|
||||
"frontend-modern/src/utils/aiFindingPresentation.ts",
|
||||
"frontend-modern/src/utils/aiProviderHealthPresentation.ts",
|
||||
"frontend-modern/src/utils/aiSettingsPresentation.ts",
|
||||
"frontend-modern/src/utils/alertActivationPresentation.ts",
|
||||
"frontend-modern/src/utils/alertFrequencyPresentation.ts",
|
||||
"frontend-modern/src/utils/alertGroupingPresentation.ts",
|
||||
"frontend-modern/src/utils/alertHistoryPresentation.ts",
|
||||
"frontend-modern/src/utils/alertIncidentPresentation.ts",
|
||||
"frontend-modern/src/utils/alertOverviewPresentation.ts",
|
||||
"frontend-modern/src/utils/alertResourceTablePresentation.ts",
|
||||
"frontend-modern/src/utils/alertSchedulePresentation.ts",
|
||||
"frontend-modern/src/utils/alertSeverityPresentation.ts",
|
||||
"frontend-modern/src/utils/alertTabsPresentation.ts",
|
||||
"frontend-modern/src/utils/alerts.ts",
|
||||
"frontend-modern/src/utils/auditLogPresentation.ts",
|
||||
"frontend-modern/src/utils/auditWebhookPresentation.ts",
|
||||
"frontend-modern/src/utils/discoveryPresentation.ts",
|
||||
"frontend-modern/src/utils/emptyStatePresentation.ts",
|
||||
"frontend-modern/src/utils/environmentLockPresentation.ts",
|
||||
"frontend-modern/src/utils/licensePresentation.ts",
|
||||
"frontend-modern/src/utils/nodeModalPresentation.ts",
|
||||
"frontend-modern/src/utils/orgUtils.ts",
|
||||
"frontend-modern/src/utils/patrolRunPresentation.ts",
|
||||
"frontend-modern/src/utils/patrolSummaryPresentation.ts",
|
||||
"frontend-modern/src/utils/platformSupportManifest.generated.ts",
|
||||
"frontend-modern/src/utils/raidPresentation.ts",
|
||||
"frontend-modern/src/utils/recoveryArtifactModePresentation.ts",
|
||||
"frontend-modern/src/utils/recoveryItemTypePresentation.ts",
|
||||
"frontend-modern/src/utils/recoveryOutcomePresentation.ts",
|
||||
"frontend-modern/src/utils/recoveryTimelineChartPresentation.ts",
|
||||
"frontend-modern/src/utils/recoveryTimelinePresentation.ts",
|
||||
"frontend-modern/src/utils/relayPresentation.ts",
|
||||
"frontend-modern/src/utils/remediationPresentation.ts",
|
||||
"frontend-modern/src/utils/resourceBadgePresentation.ts",
|
||||
"frontend-modern/src/utils/resourceChangePresentation.ts",
|
||||
"frontend-modern/src/utils/resourcePolicyPresentation.ts",
|
||||
"frontend-modern/src/utils/securityScorePresentation.ts",
|
||||
"frontend-modern/src/utils/semanticTonePresentation.ts",
|
||||
"frontend-modern/src/utils/serviceHealthPresentation.ts",
|
||||
"frontend-modern/src/utils/sourcePlatforms.ts",
|
||||
"frontend-modern/src/utils/sourceTypePresentation.ts",
|
||||
"frontend-modern/src/utils/ssoProviderPresentation.ts",
|
||||
"frontend-modern/src/utils/status.ts",
|
||||
"frontend-modern/src/utils/systemLogsPresentation.ts",
|
||||
"frontend-modern/src/utils/unifiedAgentStatusPresentation.ts",
|
||||
"frontend-modern/src/utils/updatesPresentation.ts",
|
||||
"frontend-modern/src/utils/workloadGuestPresentation.ts",
|
||||
"frontend-modern/src/utils/workloadTypePresentation.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/shared/Table.tsx": "8e5029d5ee9dddc86a4733fa1e7249793cdbedb135f3634e75112f8fb3b327c9"
|
||||
"frontend-modern/src/AppLayout.tsx": "4c421ddf417442345029db9769f7a02742c35853193fcd55d0f80605c735b0ee",
|
||||
"frontend-modern/src/api/patrol.ts": "c43045b383a0c7c7b08f6d1c11f163f827b1540988f996ea9819eb8ef9747d61",
|
||||
"frontend-modern/src/components/AI/AICostDashboard.tsx": "5fb4edb294b1fa3fd767385aedec94da5531bb4d2a83b4f8b520cb610df1fc84",
|
||||
"frontend-modern/src/components/AI/Chat/ApprovalCard.tsx": "92713fb6fa6ef2badb1d1c44fac3aa2f30da460a7da81f8c6eed3e7866349df2",
|
||||
"frontend-modern/src/components/AI/Chat/AssistantCommandHelpDialog.tsx": "7d6ed3466c0ff32796da341912eeaed4d15c95f0e357a171afe72baead89b49e",
|
||||
"frontend-modern/src/components/AI/Chat/ChatMessages.tsx": "93ce947185e031470b6d208a4736f9f5fc9effc2b8a427ea20306f2da392d3ce",
|
||||
"frontend-modern/src/components/AI/Chat/MentionAutocomplete.tsx": "830745d44c72469e93dad88dc1f0ca8a49b6b7a4c6f176c4292f1dea8b7d3e8f",
|
||||
"frontend-modern/src/components/AI/Chat/MessageItem.tsx": "0978b6d97f99cdc6cbba1076a5c105757e3a1f690d44e216e81d127c7af8d608",
|
||||
"frontend-modern/src/components/AI/Chat/ModelSelector.tsx": "c3ee6a034d0d486f0779844e006c28822d14cd022b4ff81341ba00c4f89acc79",
|
||||
"frontend-modern/src/components/AI/Chat/QuestionCard.tsx": "1cf3907918fbd674541e1a7daa369179df85cd6cab9df16e3b79255133da0028",
|
||||
"frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx": "d48683ba239d1734642a6ff2a985e50aa67ece0f03a1de4e6c452be7cb97433f",
|
||||
"frontend-modern/src/components/AI/Chat/ThinkingBlock.tsx": "dc44bf234ae14b6afdb8c8a9031051ea1eec04af1bc3a82afe174257278aa119",
|
||||
"frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx": "920e6211c322aa35893fa46ba11b0325e5d2978366a6634b8374bc0065ba27c0",
|
||||
"frontend-modern/src/components/AI/Chat/index.tsx": "a81e210aa68284c357a5d11d1a043273bee27ce37b199cae5e79c9494c82391e",
|
||||
"frontend-modern/src/components/AI/FindingsPanel.tsx": "9e6dc1d1abb0ede2b8c0db178ca03b46e1277c877dbbd06de3268d95c9052882",
|
||||
"frontend-modern/src/components/AI/aiChatUtils.ts": "3eaa28e9054fa81de1d9ef1b86076de47105e3cf17ebea7ca50f55b97664a6b1",
|
||||
"frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx": "d3b66d77a6c3d673f6f74a8e551f50e6bcc3859d8e7d7e2b2f23f9b45a7441a1",
|
||||
"frontend-modern/src/components/Alerts/AlertResourceTableMobile.tsx": "a3a2f273e3527fa792a018dd67578ea365f5b24d4fc3ec6c9a4f740e57503a07",
|
||||
"frontend-modern/src/components/Alerts/AlertResourceTableRow.tsx": "cc30f0421157dd9810bffe3fff0cc51bf5a7beb971a01016662912245e8805cf",
|
||||
"frontend-modern/src/components/Alerts/BulkEditDialog.tsx": "0f12377f7ad59dcf5a1a91957a39bfbd9a024672096703772fa6d2ca94b57255",
|
||||
"frontend-modern/src/components/Alerts/EmailProviderSelect.tsx": "db5c019341a95fd9126503ebb1c8f30c12f72411c662c653cdf5060cefb1f50e",
|
||||
"frontend-modern/src/components/Alerts/IncidentAssistantHandoffButton.tsx": "80d67f1c1b02584fd2b73535a210e201bd040672cd1297f71abc0d7a23f568a1",
|
||||
"frontend-modern/src/components/Alerts/IncidentTimelineEventCard.tsx": "0627220909c0ac2bf77e639452e4723481998231354c615729e64df8b6e6d1b1",
|
||||
"frontend-modern/src/components/Alerts/InvestigateAlertButton.tsx": "07e2cd935d6e53bb4caf7af0ad1c9b5c1a963e4d74e456cfaa0056528f8760b4",
|
||||
"frontend-modern/src/components/Alerts/ResourceTable.tsx": "0e1968d5de14f91f8d4ad7922b0d69c74e258e906d319be5d743b67bda401146",
|
||||
"frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx": "5910c6b18a0ff28d4c0a58fbdc8bf4068dc209a6a046eddd60371fc8e7268ee3",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTable.tsx": "c74b6523f09dfb5fba80e2e7e4ac3fe351d34a6c8e4db3257d40c282c89e6549",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx": "4363e45ec7dad02f89b755a6439f354a8596d4e763a5061cf2f1d4967d252fe0",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableDockerIgnoredPrefixesSection.tsx": "190def2e0d3334ee1bb11fa668a1b6d0e4d267593e44e52db3028d9f148a2e1c",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableDockerServiceGapSection.tsx": "249e29abf060f9bc6858c75aa9ddff5ad784ab3d9d54b2845041b294075e0307",
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTableDockerUpdateAlertsSection.tsx": "807195beab3c810e7d7a15a6a9bcaece2d6f2338fafe9aebb1d376584688c563",
|
||||
"frontend-modern/src/components/Alerts/WebhookConfigForm.tsx": "c00b86d5b8467ad55b2ac52cd7b9ccf71882ef9dd7f447e92399dbe642e9cb38",
|
||||
"frontend-modern/src/components/Alerts/WebhookConfigList.tsx": "2941d65e826edc72ec8f3cedfd74b5f7eb55ea23bf5f655aae43b741735ec822",
|
||||
"frontend-modern/src/components/AppBootstrapStatus.tsx": "2c68db90032bb93434357a99c46172d9ce668c5158db72a5ca1d3eb0302c743e",
|
||||
"frontend-modern/src/components/Brand/PulseBrandMark.tsx": "e3c6d88d815b6596bc378f3d6644e440b1981ff8ca2a61e285f8aad307ef5424",
|
||||
"frontend-modern/src/components/Discovery/AvailabilityProposalCard.tsx": "e3f911e0e5c6ab96de1e7e147fe6b1a741c6ba2961f09c35fcb9be0988120d6b",
|
||||
"frontend-modern/src/components/Discovery/DiscoveryTab.tsx": "7247ec7b450b4d65e82ea734a907a3a3b0529edde6ab73b6a28fef36b23c03f4",
|
||||
"frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx": "b06c9b8c444afac1e1e3fe8c685ef13c9da3338a27168b148cd7095f6aad23e9",
|
||||
"frontend-modern/src/components/ErrorBoundary.tsx": "114f1fc07f4da41dd0f70549e6edae04def1d13bcf64369a24deb1052b4256d9",
|
||||
"frontend-modern/src/components/GitHubStarBanner.tsx": "28c34c47bf97c7390f8d618c08f7f3c254a369a1b3323989b5ce5f5fff19333b",
|
||||
"frontend-modern/src/components/Infrastructure/AvailabilityProbeStatusCard.tsx": "6898faeff884ef39ec7ab8c99b6da96bf4137bffc4556c5c5be584e51bd23ad3",
|
||||
"frontend-modern/src/components/Infrastructure/ClusterDeployBanner.tsx": "a5fddf162e9beb4f1d3efe2ccde35ed41daffb0063a05da7a299bd810b4e305b",
|
||||
"frontend-modern/src/components/Infrastructure/MaintenanceVerificationSection.tsx": "ca116f9c678ebfa3cb8b2c700684d51cc7e70cb0ac0bddf4564ee4a7694e2b49",
|
||||
"frontend-modern/src/components/Infrastructure/ReportMergeModal.tsx": "f6492d36a72766b7874b34cf6cfbb53a836573104fe292644d4e3dc3c90f64ce",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceActionHistory.tsx": "4ef18bf414cc70fef647b30db62a56000e4fcf6a4ef784b0a2eaa914816f2462",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx": "2f8df87b78c619a176a98b1587e692c5a50a46d2b36f310ec6ab75c7ad68ee5d",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx": "c9b38b6dbbf1a4da8b822a43010785aeb9b7d80b4e48565917a04021919ccb9d",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx": "75f0c3c835438e4d04a8883f60614c979daba07fcfbbb9b2495274cec19b6218",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx": "5968fed2253b4c8791e69514329c9953375cce2a1767347b4f880cb5431698e0",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceDetailSummary.tsx": "ec9d97ed7bc72f4422b396c943fef546585664a0cc2fdbc06eca23826bccc1ac",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx": "cff28a266149b1d44c369b5837ec0dd9078e5f7c7829edf1417c44e7f9e8b6d8",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceInvestigationContextTables.tsx": "7e43e31ea77c9adb5b228751fd3e14b792f0735f2a8a48fdbdf4fa3e15b34559",
|
||||
"frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx": "f01cf995556c06ce66277932ff1b92971428d6cdfe0eef5494750c4de3572c1d",
|
||||
"frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx": "8a7b51c655eb1a47152c56206c53321dc07e08324f2d605c9bb35fab8c5609f1",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx": "3446e206482f10df5ce79e35ee6067019113e0e6abd940356de55019c50d5722",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx": "57d0023ba93d6720a57b5a28f29e9b33c170f98cfc00a9d980b24192b414bd08",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx": "5f4cb02af71ade8ec6c5f72bc35282ce636bc35691ff2991bced3e7f72068f41",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourceSourceBadgeCell.tsx": "057ace33675536df16da966cd5e64c7136a18dd52152670404bb959fe65af711",
|
||||
"frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts": "37215bc8215ef8819f30fc5e6a82de7ed2872217f7c8a08e07df25cc9c90119b",
|
||||
"frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx": "400e0d7756cc2c30adf651ccf6d960d1db6f40c458b428d3129ff36cc249530e",
|
||||
"frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx": "4569d7a5ba7d0ea55d680440ce1b75d0555a7213054608792414342eaa2397b7",
|
||||
"frontend-modern/src/components/Login.tsx": "2060e6846a0d1cfe069da826a0685a62375cef69b3643b72f6ee82ad162713c6",
|
||||
"frontend-modern/src/components/OrgSwitcher.tsx": "b65743de66f6a2fb4bdee0ffb27f0d754e7625209d659aed9a7be4a6890835c3",
|
||||
"frontend-modern/src/components/SecurityWarning.tsx": "1b4e66e1356c22e51649721c8314211c3bce6ce7cce7790224a50a3739157a5e",
|
||||
"frontend-modern/src/components/Settings/AIChatMaintenanceSection.tsx": "6f9b6466622cab2a847f788a8e7c7d8af6a5f8d34ac1c40197301d0b9eb6a5ef",
|
||||
"frontend-modern/src/components/Settings/AIModelSelectionSection.tsx": "ffaa33f26882e1af856c96a59c8ca7bd5471058a413b81010a0e4fad768c1aa9",
|
||||
"frontend-modern/src/components/Settings/AIProviderConfigurationSection.tsx": "35d8a2a138532e7b8d463239d007eaaa3b7665b57550e782662b9906be0e0127",
|
||||
"frontend-modern/src/components/Settings/AIRuntimeControlsSection.tsx": "0c42d95d8a4e6de980f5d2b6dbdc32085b29157157e646e0a7aa67612ef5fa86",
|
||||
"frontend-modern/src/components/Settings/AISettings.tsx": "57ca2b7a503151beb0119f884c23f7e447343e669658f4a1f9c241f7957cac45",
|
||||
"frontend-modern/src/components/Settings/AISettingsStatusAndActions.tsx": "e9c0242db3ab629cc1d29ecf1a24857dae94a56407177404b5fa7f1ea22b4e40",
|
||||
"frontend-modern/src/components/Settings/APITokenManager.tsx": "cdadce997848fd84857b51149f79592a60846012b74de51122d98e69b4f26ca2",
|
||||
"frontend-modern/src/components/Settings/APITokenManagerDialogs.tsx": "a6556fa5719503d0f05fda8a97e18f205a55bb557b8e401a99c5905c3a641606",
|
||||
"frontend-modern/src/components/Settings/AgentIntegrationsPanel.tsx": "16ec3219b16accedbd3531f75db3ff6ceaa49a9fe7fa917960e94e6b683c1f84",
|
||||
"frontend-modern/src/components/Settings/AgentProfilesPanel.tsx": "c52ee2cdbc6ca6cceaa6bf3b43ef42d55a3b8c1333125d1d488993f00650c2a9",
|
||||
"frontend-modern/src/components/Settings/AuditLogPanel.tsx": "cd8c91c0f24f7eff85c97406dc511cd8249c4a1c0efc3bdbc73d69c6e343a821",
|
||||
"frontend-modern/src/components/Settings/AuditWebhookPanel.tsx": "3851d132f455015467f13a05125ce8bdaeea744d86f09ed3068e0b852db0dbd7",
|
||||
"frontend-modern/src/components/Settings/AvailabilitySettingsPanel.tsx": "7fd0f5a6f35a7a093851b989bb63079ab03cdf451b1347367e5c02f44cad0a2e",
|
||||
"frontend-modern/src/components/Settings/BackupTransferDialogs.tsx": "f593c4a5dbbecee8f449d8004d29efcac295d594d9e84b0a44b9bd6dfa2ccf13",
|
||||
"frontend-modern/src/components/Settings/BillingAdminOrganizationsTable.tsx": "19f53dd58d2d9c7ee37667e4eb84102de688302aec099e69eb329fffc871ed43",
|
||||
"frontend-modern/src/components/Settings/BillingAdminPanel.tsx": "60f1f2a520b6e2f85916d2bf174b46c9dfae5d2e491aa34a8c3e1af927296975",
|
||||
"frontend-modern/src/components/Settings/BrandingSettingsCard.tsx": "e382ff7070fc4c01ba62ab3c366a284bfef28b1e49b5e0b9a9716401a774823c",
|
||||
"frontend-modern/src/components/Settings/ChangePasswordModal.tsx": "a28dba9ae0992ff42226fec24e77399d9eea32a3551b07d704fcdcdd6ce8ce04",
|
||||
"frontend-modern/src/components/Settings/CommercialBillingSections.tsx": "8a028ccdd9783dc421b4dd0b646806b41122e0165e7398198fcc3f2da0716abc",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx": "2a38ac312dfb4b2890012c402827dfe2b00efcb047589812f8d077fc885bb8fe",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx": "a3de7bfa65511aba00a16ef40424be988f8f72931568031882c93455b7945c03",
|
||||
"frontend-modern/src/components/Settings/DataHandlingPanel.tsx": "d99c9d682209b218f902c19c01411aa8d633ac5c4cef0fe44412fae616534daa",
|
||||
"frontend-modern/src/components/Settings/DiagnosticsPanel.tsx": "08d8b88c7a53847011c0cf70be26a70087221a3349a78dcee171f00a28abcd0a",
|
||||
"frontend-modern/src/components/Settings/DiagnosticsResultsPanel.tsx": "e841af8288e956f5a9f16e7a3573082cee3fe51eb849f5667268c745bbd423ad",
|
||||
"frontend-modern/src/components/Settings/DiscoverySettingsForm.tsx": "d4477e531b2ee23fc3cfd02cf616dd3325589c9e3121cf7001039a25418655d5",
|
||||
"frontend-modern/src/components/Settings/DockerRuntimeSettingsCard.tsx": "1c9355c33675faf4f92bd7b32ae1cec4b5713f0e7bd0726fbee3d9aa4e648ae9",
|
||||
"frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx": "58cbabb0d157332cf2ad72cf7f53bbf58ccde39f4e995a1bec93ce825ed9a804",
|
||||
"frontend-modern/src/components/Settings/GuestDockerDiscoverySettingsCard.tsx": "6ef7feef441adedb76c5659a960cfd5ee58624c4be8a7c474578a93a9f93f0fc",
|
||||
"frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx": "7a711d0626bdd171c69a738da247ef6aec8c98346057de072f84dcecea0addfe",
|
||||
"frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx": "5166d68f983bdca9f168437250f5b4f739f431204656fa901d3459b6f17439e2",
|
||||
"frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx": "6c77691095b642aee2a9d964fdb2863426cbf75b2787bc96c4807d43a3aa8580",
|
||||
"frontend-modern/src/components/Settings/InfrastructureSourcePicker.tsx": "955de90f3a7b7eae79c4822e103ed22a982403b1c2c574a922a5c662e129f160",
|
||||
"frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx": "dc287f2392c61d1099c06546d9284f3809ef7b1a382679730ba08cd6b72f88ff",
|
||||
"frontend-modern/src/components/Settings/NetworkBoundarySettingsSection.tsx": "d5cdc61740cef29f122438e84f01ead56766d5d7e937c448c9af3435b41f8ea1",
|
||||
"frontend-modern/src/components/Settings/NodeModalClusterMembersSection.tsx": "ed036203cef01e3a3a150da268f8e9da8ccda3ffa3490fcb1d590a7043c92be4",
|
||||
"frontend-modern/src/components/Settings/NodeModalMonitoringSection.tsx": "458f5b833cc2139b3adde4f8c65c7bcf42a80380f7e20af0d473649caf8e82be",
|
||||
"frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx": "0118b486d7bcc7bc38afc69eb6a345f904aae901183e8b188ef0ec9b747019fa",
|
||||
"frontend-modern/src/components/Settings/NodeModalStatusFooter.tsx": "ca0e8b456ee10b0f6b406eb7ca4cda7c75d7832e52280884f9cc9d4811c85692",
|
||||
"frontend-modern/src/components/Settings/OrganizationAccessManagementSection.tsx": "382e9b5d55fbebb721e2d6de6286f4920d87733d0259965f865de0ffaf0c23b0",
|
||||
"frontend-modern/src/components/Settings/OrganizationAccessMembersSection.tsx": "1d74c699d9304ceec29df9b19c238d9f119ba453f77707fdfd95483032dc2553",
|
||||
"frontend-modern/src/components/Settings/OrganizationOverviewDetailsSection.tsx": "9bdfa79a89c0063e8f1309f4f974ec7e1872f5ca13bac5fd06ac5cfb5f18ef52",
|
||||
"frontend-modern/src/components/Settings/OrganizationSharingCreateSection.tsx": "46625c86a8368013c971771d5e6e2354ad3a465788bab16c99fa9419721297ea",
|
||||
"frontend-modern/src/components/Settings/ProLicensePlanSection.tsx": "4e321e4e4ee2d3890651502e9ae105188d68676fe4f3857a01a84ae7bbc9ca6f",
|
||||
"frontend-modern/src/components/Settings/QuickSecuritySetup.tsx": "e98360c43b71b5ad5049e861ce65ec954a81a6985bc8ef495156c4d3ac9f7177",
|
||||
"frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx": "e0880610229f305f20a737aadb1c48b50f3c3c0f48887b7d02bd678d8ed161ec",
|
||||
"frontend-modern/src/components/Settings/RelaySettingsPanel.tsx": "dc93986289cc5d1cbb66adae4f30da4be0d6eda996b47690961597c30f3a1297",
|
||||
"frontend-modern/src/components/Settings/ReportingPanel.tsx": "a4809aaed9f5b226949029f43fb2597255633ff9d45ac74a450d723f027d9d48",
|
||||
"frontend-modern/src/components/Settings/ResourcePicker.tsx": "13d23bfb96921a5d789f6171483822ce1cdde018f5d366ce762184b15daad51d",
|
||||
"frontend-modern/src/components/Settings/RolesEditorDialog.tsx": "c3324244fcb9175d63ac7ae9e8075419157ce51f7049aa223dc8e8215738da58",
|
||||
"frontend-modern/src/components/Settings/SSOProvidersPanel.tsx": "d2f3e94a40ea352dde15954a1ed4476d2776ac97638f0199d7a8f1f39e05e781",
|
||||
"frontend-modern/src/components/Settings/SecurityAuthPanel.tsx": "3f6e389fd6a004ce63f3dd177d17c379c9cdaf466c8aed88c59e25b99fd54ba0",
|
||||
"frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx": "6ed24129944782c7a2154941460faf29ac650adcd41a01e32e937112acb6d73e",
|
||||
"frontend-modern/src/components/Settings/SelfHostedCommercialRecoverySection.tsx": "238745b0d56c32048009129e6ea2ee231950196d53e7eb6f68447d9d7b0847d7",
|
||||
"frontend-modern/src/components/Settings/SettingsPageShell.tsx": "51156ab7af99310d8d5967414d869c275d345db6c21a9c7d98855495c3c19496",
|
||||
"frontend-modern/src/components/Settings/SuggestProfileModal.tsx": "434a29fb1049a1a6c585e151af21fe936a6b4ef671cab01c68865dfbedab8e8f",
|
||||
"frontend-modern/src/components/Settings/SystemLogsPanel.tsx": "f42d2e559fa65c03b0846a28726098a2082da6206a18e2efcf33a2dd8df11ddb",
|
||||
"frontend-modern/src/components/Settings/UpdateInstallGuide.tsx": "01ab141607a534c5163a051f227f5c9e9434f845f67e3bc915b46d175b864b6e",
|
||||
"frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx": "66f4e07ea0e6198d4580e0b2079ea6358d8e750f8b6c03841dde89d2c47e1f1a",
|
||||
"frontend-modern/src/components/Settings/UserAssignmentsDialog.tsx": "cb59691911062a15913644d2de9f02c27cc86932fcf20024224aaa058171437a",
|
||||
"frontend-modern/src/components/Settings/UserAssignmentsPanel.tsx": "bf05b6645e76955054f805c7699f244e98975b0fd79a958c074f6bf118f5d567",
|
||||
"frontend-modern/src/components/Settings/availabilitySettingsModel.ts": "ca34b63a15e0b8d9ce6331439e09fd8fe76c2d44465547dc478e0d088d96c3a7",
|
||||
"frontend-modern/src/components/Settings/connectionsTableModel.ts": "5ed828ac5652c0c813cd5179b8d8dc13c9b76d59380e2f54a691ef2c4af9c601",
|
||||
"frontend-modern/src/components/Settings/useConnectionsLedger.ts": "39e5c3cfeef25c3b6198f954fe774f4ec57ac85aaf444bf787bc392c22853109",
|
||||
"frontend-modern/src/components/Settings/useProLicensePanelState.ts": "0b71e9fb64fbe946aa56058f588caaf64ed5f4d9c25ad15277f66cb4eca520a1",
|
||||
"frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx": "0dea55e92f9dd8f15c4faaaf5377b596ee9b291d2071cefaf48f31b430aa8a73",
|
||||
"frontend-modern/src/components/SetupWizard/steps/SecurityStep.tsx": "68cb0ad01917bc4096f318d3ed3129d807f297c903a6afaaa6a93f05188cfffd",
|
||||
"frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx": "cd309f7512602e4e661a74dd0da0cb10a756901e403d26fe64451fcfe1cf9781",
|
||||
"frontend-modern/src/components/Storage/EnhancedStorageBar.tsx": "053be0f27fc62284583a698b398f2cc966a66ceb75dbadeeda79d53311885194",
|
||||
"frontend-modern/src/components/Storage/StorageDetailKeyValueRow.tsx": "5fc49dda638a5dbd5abae3c6d5ef8b8328da70d24bd6a79b7a7bbf0cdfb235ce",
|
||||
"frontend-modern/src/components/Storage/StoragePoolDetail.tsx": "40b13df152ebdf819af836700d86f9c451dfd973fd54ddb1fd6e501bdebe4fec",
|
||||
"frontend-modern/src/components/Storage/StoragePoolsTable.tsx": "916448b8d29943b86d7a1904ffcd24c092872322aedcc90822a4fe369673c9d5",
|
||||
"frontend-modern/src/components/Toast/Toast.tsx": "797187aab16e3340496a1c1738bc6513f2f8c48977c123618ee3687d4a050ba6",
|
||||
"frontend-modern/src/components/TokenRevealDialog.tsx": "95cdde5f92eaa0376a7578d1e4c65e36d92fbc6e2fdad83f8aabb99b94a20171",
|
||||
"frontend-modern/src/components/UpdateBanner.tsx": "a8fdc28100f5b72718ad12dacf1047f4d15a18abdf85d534bb471a48c99f4b95",
|
||||
"frontend-modern/src/components/UpdateConfirmationModal.tsx": "f09606339879c51a1c4caf51d9fb26a8c154839cbcf01ba53a9290021df324a2",
|
||||
"frontend-modern/src/components/WhatsNewCard.tsx": "2081d440a67c1584257a3d90b6a5746ca3278e38e94082d89a65813bc6223d9d",
|
||||
"frontend-modern/src/components/Workloads/AvailabilityProbeSuggestionCard.tsx": "ae7875a04af91f5fed299553b342cd09c409f8991c3d7afc8e93164d269d2965",
|
||||
"frontend-modern/src/components/Workloads/DiskList.tsx": "fe94782c65955eea183413d90c34bf32abfdce9ff3a4642eab4904cf7117ed0c",
|
||||
"frontend-modern/src/components/Workloads/DrawerDiskListCard.tsx": "4c9af9c377f2ee5b026ff985eb754894e23dfb88525b2d327dd4afad0f00370f",
|
||||
"frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx": "f6de759fd694f1a3ebabac3067ae10604919feaa9b24bc02368a071fa7e2c438",
|
||||
"frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx": "f9f2e76ef0fad04251d0cb5873ce7df4d14d3a1ee5586daca731a2aa71addb3c",
|
||||
"frontend-modern/src/components/Workloads/GuestPhysicalDisks.tsx": "ed9f8a69c82f922dee6e1392627771578bef32d3b1f38da632b3163d7d6063c4",
|
||||
"frontend-modern/src/components/Workloads/GuestRow.tsx": "feacf9cf9d2507501d7ab83ecffdf417889e8df21664d6ced0b4cc997ab0b10e",
|
||||
"frontend-modern/src/components/Workloads/GuestRowCells.tsx": "db6dec5c11fdbf49f68d751763352ea3c30d0be51e5c0e52cdf15926b380ac25",
|
||||
"frontend-modern/src/components/Workloads/MetricMiniSparkline.tsx": "8b0eda5aa1abf8699f5222cc0463c7f7483d3500be9fd6c25b44a224d13d3d86",
|
||||
"frontend-modern/src/components/Workloads/StackedDiskBar.tsx": "d99a2dd365b6d83739612a5411b3c459c137104ebd1efb95924966ec4ae92593",
|
||||
"frontend-modern/src/components/Workloads/StackedMemoryBar.tsx": "abb1539a3c5828025fdd63667d936f726ea8e468d9c00e3d5da4664e35a90447",
|
||||
"frontend-modern/src/components/Workloads/ThresholdSlider.tsx": "21fb9b1a967008fde466770c9a863d7e700b75f3be1c50ac334ec082c165e0ac",
|
||||
"frontend-modern/src/components/Workloads/WorkloadPanel.tsx": "d0a4752f5ca91e97108275ee635641f02e91f6e8d82e3618a2f0d39fc35cc8fe",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsFilter.tsx": "a257ef62377a8475307ba6932c5b7d90d1ff2a37b82a28292a1d8c38a412df52",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsSurface.tsx": "539a443d5c139fceff17abc4d1ea3d2951714eeea8a2fbc92d87692e8d50f151",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsTable.tsx": "78d9dfe454579677ca4d5e3c2d18be69b6ef0937e357764cf0fbe681c7b29e4d",
|
||||
"frontend-modern/src/components/Workloads/useGuestRowState.ts": "457c0d5d2612baef3a2356fd7042ab999f426b8fe27cd633d607fcff607e90ba",
|
||||
"frontend-modern/src/components/patrol/ApprovalBanner.tsx": "35ef6025c20a2a8da7970cbe78fec9d7886e874ed0b2801b19da5db6d597c235",
|
||||
"frontend-modern/src/components/patrol/ApprovalSection.tsx": "16c21c9e7210593006ca7d0b7857d23f899e2b875f2ab96c2dd514f975da446a",
|
||||
"frontend-modern/src/components/patrol/InvestigationMessages.tsx": "7fdc4ff3ab278dba469e46e755756dbd15b1ef160808bfc23bd8eeb376d3e53c",
|
||||
"frontend-modern/src/components/patrol/InvestigationSection.tsx": "c9cd5b390f9fbdb805aac55c556ab6746fb310c06ef4a7841ad46a9a70a4a1ad",
|
||||
"frontend-modern/src/components/patrol/RemediationStatus.tsx": "9f18d9eae10d32063069721db0ce28ab121b9a7903d0ea1c175efe16b72c79d8",
|
||||
"frontend-modern/src/components/patrol/RunHistoryEntry.tsx": "1f7bbd1aa49afd92198d0e2a90d324cb19a8b23a8af937419feceba3e3b22218",
|
||||
"frontend-modern/src/components/patrol/RunHistoryPanel.tsx": "dc944c200a4a205a1ae953baad1afafb326abe3535e4e40f2a949c9b53ff843f",
|
||||
"frontend-modern/src/components/patrol/RunToolCallTrace.tsx": "3043cc9e08bbe096d8231e88138ab744ec744a57379c6ec49d92592c82fba039",
|
||||
"frontend-modern/src/components/shared/AIModelPicker.tsx": "2ed54c040320ec56fab38e58686f465137659aa78be019642c39607054e99a3a",
|
||||
"frontend-modern/src/components/shared/CalloutCard.tsx": "49c421188e0c955a7154b86ad81c06519d071dbf583f6f452f86bad3b2141f50",
|
||||
"frontend-modern/src/components/shared/Card.tsx": "20bd378ec801f47e5c3c48e186b7f4237280ba45dca55f7d5b8a14b03677ea8d",
|
||||
"frontend-modern/src/components/shared/ColumnPicker.tsx": "b17016f890dba4ab31f5bb0144da55e310cbeeb168342156821747d1d09f06bd",
|
||||
"frontend-modern/src/components/shared/CommandPaletteModal.tsx": "44c9b5d82a221936efddb23319e6dbd04e0b940c3aa093e6edcfc606f6a79c20",
|
||||
"frontend-modern/src/components/shared/ContainerUpdateBadge.tsx": "9f4044015315a82be336bd9125c00afcfc83fec7650e59a23e659ef9a70ce5e2",
|
||||
"frontend-modern/src/components/shared/CopyableCodeRow.tsx": "1a61d384b317bb6b1ddd56904fd6455afb54f824be8920e1cc6ac9de493eebbe",
|
||||
"frontend-modern/src/components/shared/DetailSectionTable.tsx": "df4b83fbadb66e80b84abe31a0c5e57856ba203195578b174f2915372e1a0dc9",
|
||||
"frontend-modern/src/components/shared/Dialog.tsx": "518fc81186801a45dd865e756fb952bb366fccbfed9a29c66a18cca4a0d499c4",
|
||||
"frontend-modern/src/components/shared/DiscoveryReadinessBadge.tsx": "7a68483e221612e0f79722884f4aa1c7c528efa6ad38c8c4f7f583de0d656687",
|
||||
"frontend-modern/src/components/shared/DrawerAttentionSection.tsx": "71f31900505e3356b3843200d7b0229a722ffc3ba68a43946b4bb3792ef96ff2",
|
||||
"frontend-modern/src/components/shared/ExternalTextLink.tsx": "cbc009d9a5633b345904a64bb11df4ebd2d3b96c7c00d76d5d7e8caa7f607bcf",
|
||||
"frontend-modern/src/components/shared/FeatureGateSection.tsx": "c9f3e13bc0a04b7f7190d70ae5b96b3026b59fa62555951850e3c65b15bd9261",
|
||||
"frontend-modern/src/components/shared/FilterBar/AddFilterMenu.tsx": "9e7f05884c72cce34ff69bd2c419fc2e11b520eda976ec67d62c012ef62c7ad6",
|
||||
"frontend-modern/src/components/shared/FilterBar/FilterBar.tsx": "386e265eeb8c55854d4a370c578e8fb83e1aad4c9f1dac60213b202814e12978",
|
||||
"frontend-modern/src/components/shared/FilterBar/FilterChip.tsx": "c7eb7d168b2751889fac9415662c3c7ce5dbe01e6b56482abdc8bdcd25be3546",
|
||||
"frontend-modern/src/components/shared/FilterBar/ViewOptionsDisclosure.tsx": "e2a81b990f56dc97663b1f319bafc3380f9319c2c9beea47f1594e8c2bd1081c",
|
||||
"frontend-modern/src/components/shared/FilterButtonGroup.tsx": "a6b8f14452d7272c6ec87548ed8b89f0515193b9e54dcd31903c01c4828b0265",
|
||||
"frontend-modern/src/components/shared/FilterToolbar.tsx": "864fdf324d954de6b91a0b79e2093de77eb26a6c68651f7963185673632e70c9",
|
||||
"frontend-modern/src/components/shared/Form.ts": "ba01c891aaee0ea0fb0c443382536293dd95e2fdf3dda1542eeda5359f637825",
|
||||
"frontend-modern/src/components/shared/HelpIcon.tsx": "bdf2b510632b7fb66771e00d5c5557623587feb9ee8ecc07a493b25d136c1abb",
|
||||
"frontend-modern/src/components/shared/HistoryChart.tsx": "1c19a3dcedbc257bfd8e06973c16fa89c2828c9b7e45371975f7ab6386cce58d",
|
||||
"frontend-modern/src/components/shared/HistoryChartHeader.tsx": "2b795422ca54cde763edd9b4a504493155dc8fa3935c51ebe0a63e51132adb3f",
|
||||
"frontend-modern/src/components/shared/HistoryChartOverlay.tsx": "8533e11d960193220af84b8737501a8cd5b035ac6af83b5677c9d0eb1fe38f13",
|
||||
"frontend-modern/src/components/shared/HistoryChartTooltip.tsx": "4ba8f9cb79bad7eeeb3af507375a6d246c866a31c8496901c9c6c3b5288b000d",
|
||||
"frontend-modern/src/components/shared/InfoCardFrame.tsx": "f55622fed896138ee70670c134124479416cb95bab551b04f3570724130914a8",
|
||||
"frontend-modern/src/components/shared/KeyboardShortcutsModal.tsx": "e5e6cb7bcfcd2e273b360abacfd766719351e14d756dcce02566dc1c297844ab",
|
||||
"frontend-modern/src/components/shared/MetadataBadge.tsx": "1a7c7dcd20671a2f88fa09b1c1866ed568e2d06646d602e099861bc767df720d",
|
||||
"frontend-modern/src/components/shared/MobileNavBar.tsx": "928ac70ceb8d81b87afc973b63f95ca7b1046a6e1228d59c9d2054a0e79ede64",
|
||||
"frontend-modern/src/components/shared/NodeGroupHeader.tsx": "062c7f53ceaca78588bf0d2c0bf440581ec2b572bfcabbed1663481a20a9867a",
|
||||
"frontend-modern/src/components/shared/ObjectDrawerHeader.tsx": "e3ec94868ef6f35d8c0cf6ac8c0fddde6006d539992aaf924633ebb8a7960348",
|
||||
"frontend-modern/src/components/shared/ProgressBar.tsx": "42aedbe9ace44990bd864314d66ca92ef30613ceff6b52fbb62741efd86d8b60",
|
||||
"frontend-modern/src/components/shared/PulseDataGrid.tsx": "db3696df71e4399aea7368b33a9b38488506735b05cd45741816667a6306aa90",
|
||||
"frontend-modern/src/components/shared/SearchField.tsx": "c35df7aa1a6ccc941b7a9115f3788731889d6506b7db4d8eb3d513e6f2b46bb8",
|
||||
"frontend-modern/src/components/shared/SearchTipsPopover.tsx": "77f8b9680dc122556c8e6393247e2ca5d508b0fd41fb202dd8fd0145ea12a030",
|
||||
"frontend-modern/src/components/shared/SettingsLoadingSkeleton.tsx": "7df88ef95b04cfe91cb8ccdd6fabef036c67b423aafa691b6233887164a94d81",
|
||||
"frontend-modern/src/components/shared/SkipToContentLink.tsx": "f61a08799e1df1ef022bda7c533631a0d6280b4fd72a49824bafb91fbcee5c12",
|
||||
"frontend-modern/src/components/shared/StatusDot.tsx": "874b9bd3e8b18bbd8197ed5894ca7d9c9e3ad23c5cf233f25c24797df7dbbf77",
|
||||
"frontend-modern/src/components/shared/StatusIndicatorBadge.tsx": "168cc2b605840c64cfd205c657d3acea4803f3bb065d82ca0e10fef5c61e506f",
|
||||
"frontend-modern/src/components/shared/Subtabs.tsx": "d137c44dc3f8e4f0413c77a6725bb58d8280a1362b4f32571404b97225d7e80b",
|
||||
"frontend-modern/src/components/shared/TableCardHeader.tsx": "ca640817f23a3ebed5200bc22925e46e56512ca50b242ca0b0d19e1280208af7",
|
||||
"frontend-modern/src/components/shared/TagBadges.tsx": "375a6393d6de0a259908cd99022c0a815cb4e8df2ac1767edfdbb6642df02f11",
|
||||
"frontend-modern/src/components/shared/TechnicalDetailsDisclosure.tsx": "ef8e248841efb9a392af0d5c6c0fc37102724eccea7d47a1cdb226e14d81a7ef",
|
||||
"frontend-modern/src/components/shared/TlsVerificationWarningBanner.tsx": "aedb8d55f926700d226467e148beb77dc817f82d45b9711893c1642847e016a2",
|
||||
"frontend-modern/src/components/shared/Tooltip.tsx": "a815fc91654d55d3261b6a935daab0b23426c8b803e54444d72e50e5ef3cf2b3",
|
||||
"frontend-modern/src/components/shared/TooltipPortal.tsx": "664f04c7778ae138dba8ad1047058a6963060f186031f639350eeb73a8e4d590",
|
||||
"frontend-modern/src/components/shared/WebInterfaceLink.tsx": "db9e1994f42c4ab6e8863ebee77ea1d8ab1552cb31be6b23cc80f9164b11cfea",
|
||||
"frontend-modern/src/components/shared/WebInterfaceUrlField.tsx": "b91977883999991fa8246d0e8628d43c5d425264b18fbd466d688e27a7cac4a3",
|
||||
"frontend-modern/src/components/shared/WorkloadTypeBadge.tsx": "fb8e8f2c5c44c47b3e8381fa1054e0a3aa6a4db575aa5d477fa4309e094406cf",
|
||||
"frontend-modern/src/components/shared/buttonModel.ts": "868349753949769f2485cb0470ac2812ba4eaf404e3a50a81af60678c5892bdb",
|
||||
"frontend-modern/src/components/shared/cards/NetworkInterfacesCard.tsx": "a628498b35841e0ef73b87c2aaceda458d99f5069351d1cf7a16db70286cf680",
|
||||
"frontend-modern/src/components/shared/cards/RaidCard.tsx": "ab63e4afce31a26da06825b526c1c6e40c8193f03d00c9af315950e2f9acce73",
|
||||
"frontend-modern/src/components/shared/containerUpdateBadgeModel.ts": "4a24e7b857a172892c1f4abc293de6d33b6be407476e48cacccf0947eeaa7c6e",
|
||||
"frontend-modern/src/components/shared/dialogModel.ts": "55987d316c38be24b771a8d31ae74136fd0aa13d28dd88239cfea216d391b3d9",
|
||||
"frontend-modern/src/components/shared/filterButtonGroupModel.ts": "01dd282f323a8d0ee1f85561c77d83d3c073e6372a2258964e60de019ee7f836",
|
||||
"frontend-modern/src/components/shared/groupedTableRowPresentation.ts": "3665ff989387f4261275476014ac5e8d2ee5feb652159f53087a062c30f7c495",
|
||||
"frontend-modern/src/components/shared/mobileNavBarModel.ts": "7f98d2a58c1beb9eb1e5e84dbea5717fbcf9ca68a59f06269ea423e5c6973d5b",
|
||||
"frontend-modern/src/components/shared/searchInputEnhancementsModel.ts": "4bfba0b400184d3a9eb85b3c0f04ced02eed4f171510fd246dbb8d7d6d5acf17",
|
||||
"frontend-modern/src/components/shared/searchTipsPopoverModel.ts": "7e6e85e55f2df2fa9dc5ee45d49bfa815fcf4317e47217e2ead83523fc800948",
|
||||
"frontend-modern/src/components/shared/selectablePillModel.ts": "aaf34aaf2f33d7dccd553bb69bc7dfab0cfd2e2818068caab139d2ae53846ec9",
|
||||
"frontend-modern/src/components/shared/selectionCardGroupModel.ts": "ef2c99a471eaf64553c526fbd540d84954df06de6663cd2d4a32ae632a8a7b8e",
|
||||
"frontend-modern/src/components/shared/sourcePlatformBadges.ts": "dbe107f52c57222b8e1fff249a6ccd5fe8ac0face80fb224a9c440a2c9e070bd",
|
||||
"frontend-modern/src/components/shared/statusBadgeModel.ts": "701b513c734b54066ed2db20cfd99821383e7e2b8a03bcbe7a08ca72fce650c5",
|
||||
"frontend-modern/src/components/shared/summaryInteractionA11y.ts": "f861aa2cc7f1089f44de381c15942120f5bb623f60c03ec1f342eaa76a7dfdd4",
|
||||
"frontend-modern/src/components/shared/tagInputModel.ts": "7c9e12a0b3625e901f2672c1efdf732d802d0f1d00310da4e0c46d01af106d56",
|
||||
"frontend-modern/src/components/shared/toggleModel.ts": "1a88ace8749a5958058a07f2a33b4e103166ad2b7a0d09ef08ea28af43d6fbec",
|
||||
"frontend-modern/src/features/actions/ActionDecisionPacket.tsx": "46dabe5cb9cc26d3b1614ef74d29d09d3553dc54f907626b98c9a5e7e7eadd5e",
|
||||
"frontend-modern/src/features/actions/ActionReviewDialog.tsx": "d215aea99db933f3689ae36c6a268111c10054337994461355031f068bec3834",
|
||||
"frontend-modern/src/features/alerts/AlertAppriseDestinationsSection.tsx": "39aa637b157c3e3ccf9a2c32c79e1a66488d7e5069593cd53edcd6c6464d42b3",
|
||||
"frontend-modern/src/features/alerts/AlertDeadManDestinationSection.tsx": "69dcd143f337ffd0f3d52ce97e96bf09f507bef4ae3d7923507497b088ee1760",
|
||||
"frontend-modern/src/features/alerts/AlertDeliveryHealthCard.tsx": "6a0e1f34e7cb495663e26495f83cb0c5f14c8eccfe42be077fe1daa5430fb314",
|
||||
"frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx": "22833823ac50577682312e38aba5710bc145e15e7f4b00e6c00a955c8b017495",
|
||||
"frontend-modern/src/features/alerts/AlertDeliveryPausedCard.tsx": "b0c07063973fa178cfdd6451a8aa16164f346ae59b4ba5bae350895c3ce9ae58",
|
||||
"frontend-modern/src/features/alerts/AlertDestinationsLoadErrorCard.tsx": "ccbf801dfc2122da4c854462e479a3ebfa4fe24c4ddd665e30400f8d710d530d",
|
||||
"frontend-modern/src/features/alerts/AlertDestinationsLoadingState.tsx": "6da60c07d55c19d03a1aa7142060bd543841ddf1090dd252e637530da0ee9c94",
|
||||
"frontend-modern/src/features/alerts/AlertEscalationSection.tsx": "b9cc1f3b5403c26c64c492e1b5cf6c7ae309881ea55f14823d8ec54d1667547f",
|
||||
"frontend-modern/src/features/alerts/AlertHistoryAdministrationCard.tsx": "988adf5e0bc384e33f12702d1ea75ceec53a80653c47a232a14b3b93d157ecd8",
|
||||
"frontend-modern/src/features/alerts/AlertHistoryFrequencyCard.tsx": "e2431446c099ece3116445c9fdb51259eae0b535c4c8863650beb32d63977dfa",
|
||||
"frontend-modern/src/features/alerts/AlertIncidentSynthesisSummary.tsx": "9926aae0eb5157a6acde84e7be7b50b582a7de719ad74af1bb2de38d89fa14f8",
|
||||
"frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx": "ea741de043a6489541973b08a3d8efe598724bc76e37e00f5f0fadc04c01a5cc",
|
||||
"frontend-modern/src/features/alerts/AlertOverviewAlertCard.tsx": "2f6e854f3bd1b1c4766528ab356fce96a05035fc67d17924d854bd6286f35d87",
|
||||
"frontend-modern/src/features/alerts/AlertQueueActionFeedback.tsx": "d487a98988d2aa4baa7e96a34854070b788b09fd4df06859309f2d108fdbdd9f",
|
||||
"frontend-modern/src/features/alerts/AlertSnoozeAction.tsx": "75f7d128e70b3e4665c9497e6137a37872a0cb3df34cc661b483c7dc76dc2697",
|
||||
"frontend-modern/src/features/alerts/ResourceMonitoringPolicyAction.tsx": "5dc3207d9748bcb1e532ae1207907b4fd6332b282985bff5ac832e3faceb83d1",
|
||||
"frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx": "474f2fbf49a8a46c96a46edd0549b3cf47f0c8ab9ec5596a650c4d15e4e80295",
|
||||
"frontend-modern/src/features/docker/DockerConfigsTable.tsx": "16fc39a1f246b1f664ab837a297d3934666039f4541f2918457dc7e7840993e8",
|
||||
"frontend-modern/src/features/docker/DockerContainerLifecycleControls.tsx": "dc748c7629b3db2c5b8e87f8aad124f18f64c10b5d357dd1bf3a3fdc6eda1046",
|
||||
"frontend-modern/src/features/docker/DockerHostDrawerOverview.tsx": "9f724383802f23193b008b5f821427433832cd27fd03a394481d8d4dd57491b2",
|
||||
"frontend-modern/src/features/docker/DockerHostsTable.tsx": "5e38e9e2ae6ba49a37154b23cf24a5b7ae1236c384163736d3dc473ae3c711f7",
|
||||
"frontend-modern/src/features/docker/DockerNativeTableShared.tsx": "a817cdf9268b4b6d10399dec6caf7aed4d3f175c5583a76253d9e3a8e77323b7",
|
||||
"frontend-modern/src/features/docker/DockerNetworksTable.tsx": "78dde7baff1ed642cf659754aeb4e3ca0895550a77a43463e7227cc1ee568c68",
|
||||
"frontend-modern/src/features/docker/DockerSecretsTable.tsx": "1cc37083cc10f537d2c212d83ef613409669d650d6a87fe98d559f892105badf",
|
||||
"frontend-modern/src/features/docker/DockerServicesTable.tsx": "73f8a294caed802a7da95f89962d1b610fe60bbe641ea39fbd233a09e3661181",
|
||||
"frontend-modern/src/features/docker/DockerStorageUsageTable.tsx": "8b8fb2e76e4dfee8c4a26d813068c494837c4d328deeed2b828ffeca51b732fe",
|
||||
"frontend-modern/src/features/docker/DockerTasksTable.tsx": "e74ce53b4d8e9bc7bd22632607c583b1e984f39ca2bed65d1d030a9e94c6a8c5",
|
||||
"frontend-modern/src/features/docker/DockerVolumesTable.tsx": "67461b8cd980c007b7648c2ed3b61e7a5e557f212cd138ec80426faef56f9730",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx": "8114113f67b4d6e11b087c7e6767d2f9c5944c751359cd3eff06454fb5b489c6",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx": "654f8e21a1afa4ab4e3102164e6c52c68063777d5330bbdebf72dfbbc69a89b0",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx": "db2594730d99ae22f65bf42116321d23c4fa66648f44d2dc667bf26401a77a07",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx": "d3803fa93798f509f44e00fb515a4415e966d8820c1149107064891344fa3f3b",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesEventsTable.tsx": "c615b68c4b40af4a1688086fae164a8aad7312c21f8d2de8a8936c66aa7c164d",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx": "d750ad9fe003e3edad34b135435bf09b355485f1401db82c1924dadddb57108c",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx": "ab53b41c52a2f9bb40f5b0cc90f799ba4ef70a1463e014fd3742504f243767e9",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx": "b29d65cb342ceada86265434a438031f6adb65d27cb51351f2d11ed7c566084e",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx": "dbcd7a43d41cc1fde20e4d05081fcb343578daeaebf8edd41ba501d669f0c496",
|
||||
"frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx": "14d614e5aa421a991c02ef4342bf888d044c98d043d5caa888dee757ed212339",
|
||||
"frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "17664b98217a353adc5af13964ec74f53d310abdf1962cc1435f8a189b4490b9",
|
||||
"frontend-modern/src/features/patrol/PatrolAutopilotAcknowledgementDialog.tsx": "a3673f76715848ee6f0c34292e09fdaaf7bd6d3fa45348e934a899a4e462431f",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx": "a27c9973ac925d925c9511f85da877fbbfd9aefd870e4509d699d3553be86a13",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx": "62706787851fbea76c835a8e2bb34f6f4967de33ee326501de44f9f58435985f",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx": "dcdd0e0c58df7887c341f7d5c6e900158e612c04c7beb1197168c98cda4e6085",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceWorkspace.tsx": "1770ce0c181a64e1de73ab17291216ec67d61b0e1993b2ecd8f1c33eb74099cc",
|
||||
"frontend-modern/src/features/patrol/PatrolObjectivesPanel.tsx": "407aaad855fdaee779698a2b101281c9021d079d81917cee658b13a34d6b2fd1",
|
||||
"frontend-modern/src/features/patrol/PatrolRecentWorkPanel.tsx": "22c9e85c144a50ab3055e843f10a2f83a3c35eec8af0d94bef44aebc1d33995d",
|
||||
"frontend-modern/src/features/patrol/PatrolWeeklyDigestCard.tsx": "663575526acb6b2ea2b56def0122f459b253888f3e78cfc29c9859a96406f7b5",
|
||||
"frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx": "9dfe82bde38c39e88069d026800efce66c70cc8e42afc11340dd4204a33e3cda",
|
||||
"frontend-modern/src/features/platformPage/PlatformWindowedRows.tsx": "0ee276286634abf14800d6f0f0021935ffb296307dbc6f3acd7764fa5b8ac587",
|
||||
"frontend-modern/src/features/platformPage/sharedPlatformPage.tsx": "2266e87a50c0c2fd74b644cf32ba315f379a09f96fac2f82ef4a10749a5627fd",
|
||||
"frontend-modern/src/features/proxmox/BackupActivityChart.tsx": "a2264f88229dbfd362648c201b20ccf674795f6daf4e510688bf6ec9fca9cdf2",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx": "8b2d2ec1f0a2a3fe780ddc82c502880c2485c3389e2c462e0126f158745104d3",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupsCoverageStrip.tsx": "a6cf34887798459643ba37cb01325b13709ebc33abc21ef66a688ac3239ab8fc",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx": "2ae25d7a93043d37baccfd345bc0fa02a3dac706474279f3030a95d4a947cf5f",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx": "b5333f20fe331a0ed094bd6543644c05533c1b36a0e5737f3ce4415e0072f260",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCephTable.tsx": "4733b992aea7e20f5e3e9abbe597fd22aa44953e9ee44529ea4bd2a63fa0dfd8",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx": "4f242d18904ce1205fa5e2643d35d4671c3464e374d3b26d8b921ca20df82ff4",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx": "5e297168468cb2a0ae4f9c54926dade6481e5ca137bd08ae6d84b68477bde601",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxMailGatewayTable.tsx": "073545b11f2617928563aba81b6e272f4363cb86f79dd41b1a04117888e30de8",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx": "25e2f2f5231d22920d2d2c1c4285e35dec4932ad73b6dc89857055884d2403d5",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx": "80b4104e5dea31a356a6eb55c3de1fb24cec3b808d480aea2c7ed49ae429b542",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx": "f4ac21e5a0397911c986c0fc154223b724645cbc01631a3941de43b566260c74",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxReplicationTable.tsx": "27a23f0c17288ad4f02338dcd7e4f771e932ab06d2a9ee09190d69aba9d0222a",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx": "cd3fb5015f6a58f58a6be9dd568916a465dc9c55970cf297c8f75efc4fb8434f",
|
||||
"frontend-modern/src/features/proxmox/proxmoxHostTableModel.ts": "7c77a2bd1c71753a83c55b2c9efb2d0b526f94f192439d474864723f02d57889",
|
||||
"frontend-modern/src/features/standalone/AgentsMachinesTable.tsx": "6a26fd37020b1f81304685ab2fd9d8a3f9ed1c4a9a88a0287739b661fbb87e88",
|
||||
"frontend-modern/src/features/standalone/AvailabilityFleetView.tsx": "a15aff11954b837fe6bbe7f2e5cc5701a5c42707dc70e0725d47d2cc3aed4d4a",
|
||||
"frontend-modern/src/features/standalone/StandalonePageSurface.tsx": "8a5a5bc27c8fcc2a42b75a7f775775fb32f2fc9ecd025018b810e9b80108f1dc",
|
||||
"frontend-modern/src/features/storageBackups/detailPresentation.ts": "90cb44fe5d276ea01ff5d1e9c37511ffd9ed1833e142ca3a4cc9336006d79eb4",
|
||||
"frontend-modern/src/features/storageBackups/diskPresentation.ts": "c89274cfb53e86aa9306ac1c1a1e4febf480defc7aef4317c96da2158953de34",
|
||||
"frontend-modern/src/features/storageBackups/groupPresentation.ts": "1366244532e7ddd289323af2065c2e32197e472c0572a07809557145e08585aa",
|
||||
"frontend-modern/src/features/storageBackups/storageDomain.ts": "278ae487e439509d801958e801c781e4ae6a1713a271e910a5acc5cfad9aa47c",
|
||||
"frontend-modern/src/features/storageBackups/storageFilterPresentation.ts": "5b606861c23d9f2b92b335807756510b233f9905bb15187bd3a259ddae0c2ac7",
|
||||
"frontend-modern/src/features/storageBackups/storagePagePresentation.ts": "345942741c42e036736711b7613cde53448119adb94485ab81182625dca0bc50",
|
||||
"frontend-modern/src/features/storageBackups/storageRowAlertPresentation.ts": "2f424e2349067441950b5f1da8d5b37ef42227dcc43b9dab7bf74a706f39a26c",
|
||||
"frontend-modern/src/features/storageBackups/zfsPresentation.ts": "8accab0db26582d95aa40f98497e98b41a327b514197aea86af5cb13d08cc2bb",
|
||||
"frontend-modern/src/features/truenas/TrueNASAppsTable.tsx": "04762448e46b3c1badd41fe2b9ecacef82f0282c3ea2490c4448a129bb473586",
|
||||
"frontend-modern/src/features/truenas/TrueNASServicesTable.tsx": "9c2f4ae52b2c7be725e8f64cc5f59c9058d2c248bebbb95d864fa2a998a86e14",
|
||||
"frontend-modern/src/features/truenas/TrueNASSystemsTable.tsx": "1a3c1b776852b5d718230a78589021ddfce86c61dc98c6e006f82cb9c1423231",
|
||||
"frontend-modern/src/features/vmware/VmwarePageSurface.tsx": "7fc101afebe9a55d9e60efcf5a3bca630563dc398d0f65053e5c649c862a59df",
|
||||
"frontend-modern/src/features/vmware/VsphereHostsTable.tsx": "99206612b0b2244fb0ecdbcf728395338e140eaf498daf44d7f96c08679b9e66",
|
||||
"frontend-modern/src/index.css": "58b26f9589954ca33974f308bdba1a1f70306a90894d29c7ae2a57a6140bc35e",
|
||||
"frontend-modern/src/pages/Actions.tsx": "f21b460b7346582e2af403ece22b1222b0512912cfe3ab072be2eca5a3d3a369",
|
||||
"frontend-modern/src/pages/Alerts.tsx": "815928e2758dae874ffced5968128524f2bcb358764b682f05cee68fa3a7ef23",
|
||||
"frontend-modern/src/pages/Docs.tsx": "2ddb5a1daef50d23a99a7d1cb510c73cd7a85600f07ea9f69ad72f741f3b5229",
|
||||
"frontend-modern/src/pages/PricingHandoff.tsx": "361397533fe4c7a6feb9e767a3f9004e509b74fb142438f0344614cc0b73e174",
|
||||
"frontend-modern/src/tailwind-palette.css": "70d33f3d7f50d397c9d14f07e96afaf2d76ca0d068cb11c3c3662f8f888fe8c7",
|
||||
"frontend-modern/src/utils/actionAuditPresentation.ts": "7ededfa7ee1d990c2b6fe7efa8d8c4b4d9747687182e67804b321403e31662a8",
|
||||
"frontend-modern/src/utils/agentCapabilityPresentation.ts": "f548264970ec5277e3e4677ae8879ed218dbec4cdff9edfbaceeca2020ea03e9",
|
||||
"frontend-modern/src/utils/agentProfileSuggestionPresentation.ts": "0a62c988644a571d177f854488bf14a348844db977e4c11a1bd0ac1735b47c59",
|
||||
"frontend-modern/src/utils/aiControlLevelPresentation.ts": "a1c669accdf689a1696179ca3145e06207c9f93f97f4481493aa73c5670db0cf",
|
||||
"frontend-modern/src/utils/aiFindingPresentation.ts": "1ce5cb2a13cbe8c627d22afde1923c82c39e048faddaf9d46062aa91336100e3",
|
||||
"frontend-modern/src/utils/aiProviderHealthPresentation.ts": "04875a58c288fde08e17e58528a0239fd419c0706b13ad4761b77e6563b86e9b",
|
||||
"frontend-modern/src/utils/aiSettingsPresentation.ts": "33b4970d041bbd9cbba88eba5d62bbdd3855ac388f1ca25fb7deba2aee6e9e94",
|
||||
"frontend-modern/src/utils/alertActivationPresentation.ts": "22780429fc4c944d965aee1a6241f6793c58f6b0f76397ea772896de20929ba0",
|
||||
"frontend-modern/src/utils/alertFrequencyPresentation.ts": "03139ee758052b838d0fc7c86a41b7b4c21030e06516584adedfa75825fa8755",
|
||||
"frontend-modern/src/utils/alertGroupingPresentation.ts": "ee0e2b7c7f357c817d1ae8bc5c962b3c3a0955d43d62948bdd44b1035fc45698",
|
||||
"frontend-modern/src/utils/alertHistoryPresentation.ts": "560b4571b97dd5b63c3e1d444da1b93dd3bc494bf8ba9c022b0f9d5a6fe18abe",
|
||||
"frontend-modern/src/utils/alertIncidentPresentation.ts": "9ab65844b92b1a765a10f33100058a0268e4681cb61b4aec70f9db35d471910e",
|
||||
"frontend-modern/src/utils/alertOverviewPresentation.ts": "e17c4a7030b6b2b50c59624e563e5c413ab981bb39f066f0f7cad482d4373798",
|
||||
"frontend-modern/src/utils/alertResourceTablePresentation.ts": "337fad4877039214a332ddfc6c2c9ab1cc02d329251c8a9885a5266da4ef933b",
|
||||
"frontend-modern/src/utils/alertSchedulePresentation.ts": "d0921d895d003084b1c15dcff172a56ba889451c4f7ddce787abb064318e8c74",
|
||||
"frontend-modern/src/utils/alertSeverityPresentation.ts": "23362496c93c96371f055f1a216fca2a0d465485ee2448203ac1d3f83b35cd9a",
|
||||
"frontend-modern/src/utils/alertTabsPresentation.ts": "7617902999621e3af01f27f68b74384467e285f7007ecc5ca0150fd6142e48b3",
|
||||
"frontend-modern/src/utils/alerts.ts": "628a7a41b5546bc3c05ce19c254cd3bc1c2aa683b71716133f9c2ad281f339c2",
|
||||
"frontend-modern/src/utils/auditLogPresentation.ts": "fd7204b3b8dc1932f35b444433f7007b8a25e641d296b2108668ab7d8b0779ad",
|
||||
"frontend-modern/src/utils/auditWebhookPresentation.ts": "dce90b54d19282bd3001b228da5d47a664ecdcbfb990d630808adffe772d1710",
|
||||
"frontend-modern/src/utils/discoveryPresentation.ts": "15c2b0acbfff32d0d16db4aed473087afbdd5b2e46cc47d614c1863051d57c97",
|
||||
"frontend-modern/src/utils/emptyStatePresentation.ts": "244354879e9f476e5245f982b607375765b74b54473111e43b4185d7396f255e",
|
||||
"frontend-modern/src/utils/environmentLockPresentation.ts": "5044e5d00f5b3775b229c6c45b37b48b2ae421d1e6d5b6d34c9dc11d827f7625",
|
||||
"frontend-modern/src/utils/licensePresentation.ts": "b6765aaf7c535f9bafa5668d8b5d4f78ff8295ea184c389c28095e63dceaf425",
|
||||
"frontend-modern/src/utils/nodeModalPresentation.ts": "c2dbc6ab6b7588c6d01a102ffec18105d1cd589e908c6e4f70465fd6f1751d1e",
|
||||
"frontend-modern/src/utils/orgUtils.ts": "920a53fef0f4980c923437f11d6e34418666835f2697450fefa1fa9f586169ba",
|
||||
"frontend-modern/src/utils/patrolRunPresentation.ts": "931ddf0759bf999deb288a4234a5c6c3cc1254217b39f0d2c992b4cca866644d",
|
||||
"frontend-modern/src/utils/patrolSummaryPresentation.ts": "338fc8fd68f0a412f88ea138ca1d2ef9679c5b2b37483cad8a51a888c5ee8c6e",
|
||||
"frontend-modern/src/utils/platformSupportManifest.generated.ts": "68b2c2e0c3f0f66ac9d0cb81d922066b18da99824f7799997f1efea84ca444cf",
|
||||
"frontend-modern/src/utils/raidPresentation.ts": "affdc07e317093024688c32757ec2ae36ae822a52460030ce203b966a576e403",
|
||||
"frontend-modern/src/utils/recoveryArtifactModePresentation.ts": "6d171805355a668d4c0a99889ded4f587f90fc1ceb1b4d7d7e424b46627615c5",
|
||||
"frontend-modern/src/utils/recoveryItemTypePresentation.ts": "f897246ebce4cb5d2a2201f02787b24bc999098fe8024b98bc87025b509922b0",
|
||||
"frontend-modern/src/utils/recoveryOutcomePresentation.ts": "7b96feadef679031c7bd64c6034f2d1eb7a8fed892cb9705babd8a9546e9ec36",
|
||||
"frontend-modern/src/utils/recoveryTimelineChartPresentation.ts": "36980c4ffb2292beeeaa3e45aa075fb135e2e8be75755bacbac59c9b19ddc141",
|
||||
"frontend-modern/src/utils/recoveryTimelinePresentation.ts": "a63caa8fdf67e4e15f82f8818314663081f16433f01950754802d17b4ac3322e",
|
||||
"frontend-modern/src/utils/relayPresentation.ts": "cf668ef232330d9de915129baff5be7f939d7cf8f4d1eaed96f24b375230f1a7",
|
||||
"frontend-modern/src/utils/remediationPresentation.ts": "2bfa6550311500f7f42c69f57f48ce9e43263b1f514f1d85067e9ec3e2291f2b",
|
||||
"frontend-modern/src/utils/resourceBadgePresentation.ts": "2cd546618528d3421490291a0dfb63ca2d6fd6a2735d48872b4fe4f60accf582",
|
||||
"frontend-modern/src/utils/resourceChangePresentation.ts": "9dec581a704f25c4891efd9a4bd1be7814536d79df2b59daef84f84c5aad9086",
|
||||
"frontend-modern/src/utils/resourcePolicyPresentation.ts": "08300bb46f436cac1b2e2c9841e34f0e54a0a23b34027463d6d85b289885b918",
|
||||
"frontend-modern/src/utils/securityScorePresentation.ts": "ff4a97c9d884197072fdf4bcaef0ab9b3a8eeabd323ce9e29515a2c3112eadab",
|
||||
"frontend-modern/src/utils/semanticTonePresentation.ts": "6e8e58f7184d887529e6873679b45b569662ba5a993a592262947d03a3603fd2",
|
||||
"frontend-modern/src/utils/serviceHealthPresentation.ts": "8d4acb9257fb04bf22fb1ecf8baa76be8bd73d4f8ff7a30613d523f6fd9112d2",
|
||||
"frontend-modern/src/utils/sourcePlatforms.ts": "d2e23d3e6f0bd0b85f5c5cf44363c89a62d92ab9d87cdbfe09aea32608874e77",
|
||||
"frontend-modern/src/utils/sourceTypePresentation.ts": "ea6b7e2ab9d28f64096761828abffe2d1d28621f29a23eb16ef3cd6877d33149",
|
||||
"frontend-modern/src/utils/ssoProviderPresentation.ts": "bebd1e6a80e91ba2f382f51fc92ee21219a2d8ddbfc25cca835687b2273bc97f",
|
||||
"frontend-modern/src/utils/status.ts": "47aa5db56107290cd573732cc2d080a6cbb518e3c50a8e733a06809516c701fe",
|
||||
"frontend-modern/src/utils/systemLogsPresentation.ts": "3e4e34d9642eb8a9b30499ff5dde2a98c7ca92020ee02ff53b59fb6c121797a1",
|
||||
"frontend-modern/src/utils/unifiedAgentStatusPresentation.ts": "2040add4e038d43b1576d08ec8e3a5c701e11b55f4edd73df035ce033a3468e0",
|
||||
"frontend-modern/src/utils/updatesPresentation.ts": "64d91c2e62f812ec20f7e28eae6a0189b2bcc09dc1b56b2df719c1a443def63c",
|
||||
"frontend-modern/src/utils/workloadGuestPresentation.ts": "7ac58b7802065bb87ee07c64c609714b219e243d981be174c17dfe179e34a69c",
|
||||
"frontend-modern/src/utils/workloadTypePresentation.ts": "18289c2ee223c8ae28bdacc099993aa45b00642ea6b7b10ae0128582c9886e03"
|
||||
},
|
||||
"routes": [
|
||||
"/proxmox/overview",
|
||||
"/proxmox/storage",
|
||||
"/proxmox/replication",
|
||||
"/proxmox/backups/date",
|
||||
"/proxmox/ceph",
|
||||
"/proxmox/mail",
|
||||
"/docker/overview",
|
||||
"/docker/images",
|
||||
"/docker/storage",
|
||||
"/docker/networks",
|
||||
"/docker/swarm",
|
||||
"/kubernetes/overview",
|
||||
"/kubernetes/nodes",
|
||||
"/kubernetes/workloads",
|
||||
"/kubernetes/services",
|
||||
"/kubernetes/storage",
|
||||
"/kubernetes/configuration",
|
||||
"/kubernetes/events",
|
||||
"/truenas/overview",
|
||||
"/truenas/storage",
|
||||
"/truenas/services",
|
||||
"/truenas/apps",
|
||||
"/truenas/vms",
|
||||
"/truenas/shares",
|
||||
"/truenas/protection",
|
||||
"/vmware/overview",
|
||||
"/vmware/storage",
|
||||
"/vmware/networks",
|
||||
"/vmware/health",
|
||||
"/vmware/activity",
|
||||
"/standalone/machines",
|
||||
"/standalone/availability",
|
||||
"/alerts/overview",
|
||||
"/alerts/history",
|
||||
"/alerts/thresholds",
|
||||
"/alerts",
|
||||
"/patrol",
|
||||
"/actions",
|
||||
"/settings/infrastructure",
|
||||
"/settings/monitoring",
|
||||
"/settings/pulse-intelligence",
|
||||
"/settings/organization",
|
||||
"/settings/security-users",
|
||||
"/settings/security-webhooks"
|
||||
"/docs/TROUBLESHOOTING"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 900,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 700,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 560,
|
||||
"height": 900
|
||||
"height": 1600
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
"height": 900
|
||||
"height": 1400
|
||||
},
|
||||
{
|
||||
"width": 800,
|
||||
"height": 1400
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Mock stack on origin/main plus this change; Playwright sweep of every table on the routes above at all six viewports, before and after, recording computed th/td padding per cell and leaf elements with scrollWidth > clientWidth under hidden overflow (455 table states compared)",
|
||||
"Default TableCell/TableHead with no caller padding keep 8px/12px horizontal and 2px/6px vertical padding (Settings infrastructure source and agent tables unchanged at 12px/12px/6px/6px)",
|
||||
"Platform tables (Proxmox nodes, Docker hosts and containers, Kubernetes clusters/nodes/deployments/pods, TrueNAS systems, vSphere, Machines): cells 12px -> 8px horizontal at 700px and up and 8px -> 6px at 560/390px; header cells keep the stylesheet 6px horizontal and drop to 2px vertical, header rows 29px -> 21px; row height 32px unchanged; clipped-cell count improved in 89 table states, unchanged in 362; the 4 states counted worse are live mock-value drift (relative-time and usage labels) with identical or wider content boxes",
|
||||
"Physical Disks table (Proxmox > Storage > Physical Disks): cells 8px -> 4px horizontal at 390px and 12px -> 6px at 700px, matching the composed px-1 sm:px-1.5 lg:px-2; clipped cells at 390px 93 -> 18; detail row 12px -> 16px horizontal at 700px and 1440px",
|
||||
"Storage pools table unchanged at 6px/8px cell padding across 390, 560 and 700px",
|
||||
"Inline detail rows (Proxmox node, Kubernetes controller, TrueNAS system expansions): detail cell padding 12px/12px/2px/2px -> 0, detail panel inset now only its own px-2 sm:px-4; incident timeline panel in Alert History gets its p-3",
|
||||
"Alerts overview stats table: dot column right padding 12px -> 0 and value column 12px -> 6px at 700px and up as its classes name; Alert History header cells 12px -> 8px horizontal; AI cost table (Settings > Pulse Intelligence) Est. USD column 12px -> 4px as its px-1 names",
|
||||
"Workloads table header cells now 6px/8px to match their raw td data cells; Proxmox replication compact-mode !px-1 override still applies at 390px",
|
||||
"Reactive case: switching a cell class between px-1 and a non-padding class re-resolves the base padding (unit test)"
|
||||
"Private mock stack: main (Tailwind v3) and this change (Tailwind v4) served side by side from one backend; Playwright loaded each route on both and compared every element's computed style plus a full-page screenshot pixel diff",
|
||||
"64 route/theme/viewport states (16 routes x light and dark x 1440 and 390 with touch emulation at 390): 59 pixel-identical; the rest are mock drift (alert counts, last-refresh and last-scanned timestamps, tab-strip scroll timing) verified identical on rerun, plus 12 antialiased edge pixels on vSphere at 390",
|
||||
"Docs page: identical pixels; prose text colors differ by at most 2/255 per channel because @tailwindcss/typography reads Tailwind 4's own palette",
|
||||
"Machines at 390: row action button now renders its declared 44x44 touch target (28x28 under v3, whose lexical sort let the base min-h-6 win) while rows stay 32px; pixel-identical to main",
|
||||
"Runtime scan of 82 states (every tab one level under each platform, Alerts, Patrol, Actions and Settings, with the first row expanded, at 1440 and 390) plus a static scan of shared-primitive call sites for class pairs whose cascade winner differs between v3 and v4: touch-target overrides keep their authored 44px phone size, cosmetic overrides that v3 never applied were removed so they keep rendering as today, and overrides v3 did apply use the v4 important suffix",
|
||||
"Proxmox guest drawer and Docker host drawer at 1440 and the Proxmox storage table header at 800: pixel-identical",
|
||||
"npm audit --json over the full graph: 0 findings; braces, micromatch, fast-glob and chokidar absent from the lockfile"
|
||||
],
|
||||
"interactions": [
|
||||
"Load each route at 1440, 1280, 900, 700, 560 and 390px and run the padding and clip sweep over all tables",
|
||||
"Click Physical Disks on Proxmox > Storage at 390, 560, 700 and 1440px and expand the first disk row",
|
||||
"Expand the first node row on Proxmox > Overview, the first deployment row on Kubernetes > Workloads, the first host row on Docker > Overview and the first system row on TrueNAS > Overview at 390, 560, 700 and 1440px",
|
||||
"Expand the first alert row incident timeline on Alerts > History at 1440px",
|
||||
"Compare before and after JSON sweeps per route, tab, viewport and table; re-measure the four states counted worse three times each",
|
||||
"Revert and restore the change on the running dev server to A/B the Kubernetes clusters memory bar label at 560px"
|
||||
"Hovered the second table row on Proxmox, Docker and Alerts at 1440 and at 390 with touch emulation in light and dark: identical row background and pixels (hover is not limited to hover-capable pointers)",
|
||||
"Keyboard-focused the search field with Shift+Tab then Tab: same 2px blue focus ring; the transparent 2px outline becomes outline-style none (outline-hidden keeps it in forced-colors mode)",
|
||||
"Expanded the first row on Proxmox and Docker at 1440 and 390: identical pixels apart from timestamp drift",
|
||||
"Measured the Machines row action button and row height at 390 and 1440 on both builds",
|
||||
"Opened the Snooze menu on an Alerts card at 1440 on both builds: identical pixels"
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+726
-1607
File diff suppressed because it is too large
Load Diff
@@ -72,22 +72,21 @@
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@solidjs/testing-library": "^0.8.5",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@types/node": "^26.6.2",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
||||
"@typescript-eslint/parser": "^8.70.0",
|
||||
"@vitest/coverage-v8": "^4.1.11",
|
||||
"autoprefixer": "^10.6.1",
|
||||
"eslint": "^9.20.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-solid": "^0.18.0",
|
||||
"globals": "^17.12.0",
|
||||
"jscpd": "^4.0.8",
|
||||
"jscpd": "^5.4.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"postcss": "^8.5.28",
|
||||
"prettier": "3.9.8",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^5.3.0",
|
||||
"typescript-eslint": "^8.70.0",
|
||||
"vite": "^6.4.3",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -265,7 +265,7 @@
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"const icons = {",
|
||||
"flex-shrink-0 text-muted hover:text-base-content hover:bg-surface rounded-md p-1.5 transition-all duration-200"
|
||||
"shrink-0 text-muted hover:text-base-content hover:bg-surface rounded-md p-1.5 transition-all duration-200"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -507,7 +507,7 @@
|
||||
"forbiddenPatterns": [
|
||||
{
|
||||
"path": "src/components/Settings/AIProviderConfigurationSection.tsx",
|
||||
"patterns": ["rounded border border-red-200", "bg-red-50 dark:bg-red-900 px-2 py-1.5"]
|
||||
"patterns": ["rounded-sm border border-red-200", "bg-red-50 dark:bg-red-900/25 px-2 py-1.5"]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/AddressProbeStep.tsx",
|
||||
@@ -549,7 +549,7 @@
|
||||
"path": "src/components/Settings/DiscoverySettingsForm.tsx",
|
||||
"patterns": [
|
||||
"rounded-md border border-amber-200 bg-amber-50/80",
|
||||
"text-amber-900 dark:text-amber-100"
|
||||
"text-amber-900/25 dark:text-amber-100"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -573,8 +573,8 @@
|
||||
"path": "src/components/ErrorBoundary.tsx",
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"bg-red-50 dark:bg-red-900 border border-red-200",
|
||||
"p-4 bg-red-50 dark:bg-red-900 border border-red-200"
|
||||
"bg-red-50 dark:bg-red-900/25 border border-red-200",
|
||||
"p-4 bg-red-50 dark:bg-red-900/25 border border-red-200"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -602,8 +602,8 @@
|
||||
"path": "src/components/UpdateConfirmationModal.tsx",
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"bg-blue-50 dark:bg-blue-900 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900 border border-yellow-200",
|
||||
"bg-blue-50 dark:bg-blue-900/25 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900/25 border border-yellow-200",
|
||||
"rounded-md p-4 border",
|
||||
"px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
]
|
||||
@@ -612,10 +612,10 @@
|
||||
"path": "src/components/UpdateProgressModal.tsx",
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"bg-red-50 dark:bg-red-900 border border-red-200",
|
||||
"bg-blue-50 dark:bg-blue-900 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900 border border-yellow-200",
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded"
|
||||
"bg-red-50 dark:bg-red-900/25 border border-red-200",
|
||||
"bg-blue-50 dark:bg-blue-900/25 border border-blue-200",
|
||||
"bg-yellow-50 dark:bg-yellow-900/25 border border-yellow-200",
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-sm"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -644,14 +644,14 @@
|
||||
"path": "src/features/platformPage/PlatformOutdatedAgentNotice.tsx",
|
||||
"patterns": [
|
||||
"rounded-lg border border-amber-300 bg-amber-50",
|
||||
"text-amber-900 underline-offset-2"
|
||||
"text-amber-900/25 underline-offset-2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/platformPage/PlatformOutdatedSensorSetupNotice.tsx",
|
||||
"patterns": [
|
||||
"rounded-lg border border-amber-300 bg-amber-50",
|
||||
"text-amber-900 underline-offset-2"
|
||||
"text-amber-900/25 underline-offset-2"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -676,8 +676,8 @@
|
||||
"patterns": [
|
||||
"<svg",
|
||||
"<button",
|
||||
"bg-blue-50 dark:bg-blue-900 border-b border-blue-200 dark:border-blue-800",
|
||||
"p-1 hover:bg-blue-100 dark:hover:bg-blue-800 rounded text-blue-600"
|
||||
"bg-blue-50 dark:bg-blue-900/25 border-b border-blue-200 dark:border-blue-800",
|
||||
"p-1 hover:bg-blue-100 dark:hover:bg-blue-800 rounded-sm text-blue-600"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -705,9 +705,9 @@
|
||||
"<button",
|
||||
"toneClasses",
|
||||
"buttonClasses",
|
||||
"bg-amber-50 dark:bg-amber-900 border-b border-amber-200",
|
||||
"bg-red-50 dark:bg-red-900 border-b border-red-200",
|
||||
"p-1 rounded transition-colors opacity-70 hover:opacity-100"
|
||||
"bg-amber-50 dark:bg-amber-900/25 border-b border-amber-200",
|
||||
"bg-red-50 dark:bg-red-900/25 border-b border-red-200",
|
||||
"p-1 rounded-sm transition-colors opacity-70 hover:opacity-100"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -1195,20 +1195,20 @@
|
||||
{
|
||||
"path": "src/components/Settings/OrganizationAccessMembersSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/OrganizationIncomingSharesSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-emerald-700 hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900 disabled:cursor-not-allowed disabled:opacity-60",
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-emerald-700 hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900/25 disabled:cursor-not-allowed disabled:opacity-60",
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/OrganizationOutgoingSharesSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1255,7 +1255,7 @@
|
||||
{
|
||||
"path": "src/components/Settings/SecurityAuthPanel.tsx",
|
||||
"patterns": [
|
||||
"w-full sm:w-auto px-3 py-2 text-xs font-medium rounded-md border border-amber-300 text-amber-800 bg-amber-100 hover:bg-amber-200 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900 dark:hover:bg-amber-800",
|
||||
"w-full sm:w-auto px-3 py-2 text-xs font-medium rounded-md border border-amber-300 text-amber-800 bg-amber-100 hover:bg-amber-200 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900/25 dark:hover:bg-amber-800",
|
||||
"w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
|
||||
"w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium border border-border text-base-content rounded-md hover:bg-surface-hover transition-colors"
|
||||
]
|
||||
@@ -1270,7 +1270,7 @@
|
||||
"px-4 py-2 text-sm font-medium text-base-content border border-border rounded-md hover:bg-surface-hover",
|
||||
"px-4 py-2 text-sm font-medium bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50",
|
||||
"px-4 py-2 text-sm font-medium bg-red-600 text-white rounded-md hover:bg-red-700",
|
||||
"px-2 py-1 text-xs font-medium text-muted bg-surface-hover rounded hover:bg-surface-hover flex items-center gap-1"
|
||||
"px-2 py-1 text-xs font-medium text-muted bg-surface-hover rounded-sm hover:bg-surface-hover flex items-center gap-1"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1283,17 +1283,17 @@
|
||||
"path": "src/components/Settings/InfrastructureInstallerSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center justify-center rounded-md bg-emerald-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700",
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900 transition-colors hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900 hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium text-emerald-900 hover:bg-emerald-100"
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900/25 transition-colors hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900/25 hover:bg-emerald-100",
|
||||
"inline-flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium text-emerald-900/25 hover:bg-emerald-100"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/ErrorBoundary.tsx",
|
||||
"patterns": [
|
||||
"px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700",
|
||||
"px-4 py-2 bg-slate-600 text-white rounded hover:bg-slate-700",
|
||||
"text-xs px-2 py-1 bg-red-600 text-white rounded hover:bg-red-700"
|
||||
"px-4 py-2 bg-blue-600 text-white rounded-sm hover:bg-blue-700",
|
||||
"px-4 py-2 bg-slate-600 text-white rounded-sm hover:bg-slate-700",
|
||||
"text-xs px-2 py-1 bg-red-600 text-white rounded-sm hover:bg-red-700"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1306,7 +1306,7 @@
|
||||
{
|
||||
"path": "src/components/UpdateProgressModal.tsx",
|
||||
"patterns": [
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded",
|
||||
"mt-2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-sm",
|
||||
"px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
]
|
||||
}
|
||||
@@ -1430,35 +1430,35 @@
|
||||
{
|
||||
"path": "src/components/AI/Chat/index.tsx",
|
||||
"patterns": [
|
||||
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted",
|
||||
"flex-shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors",
|
||||
"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"rounded p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900 dark:hover:text-blue-300",
|
||||
"order-2 flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-none",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:text-amber-200 dark:hover:bg-amber-900",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-sm transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45",
|
||||
"flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted",
|
||||
"shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors",
|
||||
"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"rounded-sm p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900/25 dark:hover:text-blue-300",
|
||||
"order-2 flex h-9 w-9 shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-0",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900/25",
|
||||
"flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:text-amber-200 dark:hover:bg-amber-900/25",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 dark:text-blue-200 dark:hover:bg-blue-900/60",
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-xs transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45",
|
||||
"flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/AI/Chat/MessageItem.tsx",
|
||||
"patterns": [
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 focus:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 focus:bg-blue-100 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/standalone/AgentsMachinesTable.tsx",
|
||||
"patterns": [
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/SSOProvidersPanel.tsx",
|
||||
"patterns": [
|
||||
"p-2 text-slate-500 hover:text-blue-600 hover:bg-surface-hover rounded-md transition-colors",
|
||||
"p-2 text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900 rounded-md transition-colors",
|
||||
"p-2 text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/25 rounded-md transition-colors",
|
||||
"class=\"text-slate-400 hover:text-base-content\""
|
||||
]
|
||||
},
|
||||
@@ -1466,7 +1466,7 @@
|
||||
"path": "src/components/Settings/RolesPanel.tsx",
|
||||
"patterns": [
|
||||
"p-1.5 rounded-md text-slate-500 hover:text-blue-600 hover:bg-surface-hover dark:hover:text-blue-300",
|
||||
"p-1.5 rounded-md text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900"
|
||||
"p-1.5 rounded-md text-slate-500 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/25"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -1498,8 +1498,8 @@
|
||||
"patterns": [
|
||||
"lucide-solid/icons/copy",
|
||||
"lucide-solid/icons/check';",
|
||||
"mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content",
|
||||
"ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content"
|
||||
"mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content",
|
||||
"ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1507,7 +1507,7 @@
|
||||
"patterns": [
|
||||
"lucide-solid/icons/copy",
|
||||
"lucide-solid/icons/check';",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-sm border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1574,7 +1574,7 @@
|
||||
{
|
||||
"path": "src/components/Settings/ProLicensePlanSection.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 mt-3 min-h-10 sm:min-h-9 rounded-md border border-current/20 px-3 py-2 text-xs font-medium hover:bg-black/5 dark:hover:bg-white/5",
|
||||
"inline-flex items-center gap-1 mt-3 min-h-10 sm:min-h-9 rounded-md border px-3 py-2 text-xs font-medium hover:bg-black/5 dark:hover:bg-white/5",
|
||||
"inline-flex items-center gap-1 mt-4 min-h-10 sm:min-h-9 rounded-md border border-border px-3 py-2 text-xs font-medium text-base-content hover:bg-surface-hover"
|
||||
]
|
||||
}
|
||||
@@ -1657,7 +1657,7 @@
|
||||
"path": "src/components/Discovery/DiscoveryTab.tsx",
|
||||
"patterns": [
|
||||
"const CopyableCodeRow",
|
||||
"flex items-start gap-2 rounded bg-surface-alt px-2 py-1.5"
|
||||
"flex items-start gap-2 rounded-sm bg-surface-alt px-2 py-1.5"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -2022,7 +2022,7 @@
|
||||
{
|
||||
"path": "src/components/AI/FindingsPanel.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded-sm",
|
||||
"getFindingStatusBadgeClasses",
|
||||
"getFindingSourceBadgeClasses",
|
||||
"getFindingLoopStateBadgeClasses",
|
||||
@@ -2034,51 +2034,51 @@
|
||||
{
|
||||
"path": "src/components/patrol/ApprovalBanner.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded-sm",
|
||||
"firstApprovalRisk()!.badgeClass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/ApprovalSection.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 text-[10px] font-medium rounded-sm",
|
||||
"approvalRisk.badgeClass",
|
||||
"fixRisk.badgeClass",
|
||||
"bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300"
|
||||
"bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/InvestigationSection.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded",
|
||||
"px-1.5 py-0.5 border text-[10px] font-medium rounded-sm",
|
||||
"getInvestigationStatusBadgeClasses",
|
||||
"getInvestigationOutcomeBadgeClasses",
|
||||
"rounded border border-border bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted",
|
||||
"rounded bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
"rounded-sm border border-border bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted",
|
||||
"rounded-sm bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/RunHistoryEntry.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium",
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-50"
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-sm bg-blue-50"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/patrol/RunToolCallTrace.tsx",
|
||||
"patterns": [
|
||||
"px-1.5 py-0.5 rounded text-[10px] font-medium",
|
||||
"px-1.5 py-0.5 rounded-sm text-[10px] font-medium",
|
||||
"getToolCallResultBadgeClass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/proxmox/proxmoxBackupsTableShared.tsx",
|
||||
"patterns": [
|
||||
"inline-flex items-center rounded-sm px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"inline-flex items-center rounded-xs px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"presentation().badgeClassName",
|
||||
"rounded-sm bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-sm bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-sm bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
"rounded-xs bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-xs bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold",
|
||||
"rounded-xs bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3226,7 +3226,7 @@
|
||||
"patterns": [
|
||||
"const viewButtonClass",
|
||||
"inline-flex items-center gap-1 rounded-md border border-border bg-surface p-1",
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-sm px-3 text-xs font-medium transition-colors"
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-xs px-3 text-xs font-medium transition-colors"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -3251,7 +3251,7 @@
|
||||
"patterns": [
|
||||
"inline-flex min-h-10 sm:min-h-10 items-center rounded-full border px-3 py-2 text-sm font-semibold transition",
|
||||
"min-h-10 sm:min-h-10 rounded-full border px-3 py-2 text-sm font-semibold transition",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-sm",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-xs",
|
||||
"hover:border-blue-400 hover:text-blue-600 dark:hover:border-blue-400 dark:hover:text-blue-200"
|
||||
]
|
||||
}
|
||||
@@ -3385,7 +3385,7 @@
|
||||
"scopes": ["src/features/standalone/AgentsMachinesTable.tsx"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Standalone machine action triggers compose ActionIconButton for shared icon-action size, tone, focus, title, and accessible-label behavior.",
|
||||
"allowedPaths": [],
|
||||
@@ -3408,7 +3408,7 @@
|
||||
"scopes": ["src/components/AI/Chat/MessageItem.tsx"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 focus:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 focus:bg-blue-100 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat queued message icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3431,7 +3431,7 @@
|
||||
"scopes": ["src/components/AI/Chat"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content"
|
||||
"flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat header icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3457,7 +3457,7 @@
|
||||
"scopes": ["src/components/AI/Chat"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950"
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat accent icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3483,7 +3483,7 @@
|
||||
"scopes": ["src/components/AI/Chat"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-sm transition-colors hover:bg-blue-700"
|
||||
"flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-xs transition-colors hover:bg-blue-700"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat primary icon actions belong to ActionIconButton.",
|
||||
"allowedPaths": [],
|
||||
@@ -3562,7 +3562,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-7 w-7",
|
||||
"border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content",
|
||||
"border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content",
|
||||
"CopyIcon class=\"h-3.5 w-3.5\""
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat message copy actions belong to CopyValueButton.",
|
||||
@@ -3586,7 +3586,7 @@
|
||||
"scopes": ["src/components/AI/Chat/ToolExecutionBlock.tsx"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30",
|
||||
"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-sm border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30",
|
||||
"CopyIcon class=\"h-3 w-3\""
|
||||
],
|
||||
"legacyReason": "Retired migration debt. AI Chat tool copy actions belong to CopyValueButton.",
|
||||
@@ -4027,7 +4027,7 @@
|
||||
},
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".ts", ".tsx"],
|
||||
"allPatterns": ["rounded border border-border bg-surface p-3 shadow-sm"],
|
||||
"allPatterns": ["rounded-sm border border-border bg-surface p-3 shadow-xs"],
|
||||
"legacyReason": "Retired migration debt. Repeated compact information card frames must use InfoCardFrame; local card-shell copies are not allowed.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5141,7 +5141,7 @@
|
||||
},
|
||||
"scopes": ["src/components/AI"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded-sm"],
|
||||
"legacyReason": "Retired migration debt. Patrol finding row state, source, severity, investigation, confidence, and regression chips use MetadataBadge for shared shell, sizing, tone, and outline behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5161,7 +5161,7 @@
|
||||
},
|
||||
"scopes": ["src/components/patrol"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded"],
|
||||
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded-sm"],
|
||||
"legacyReason": "Retired migration debt. Patrol investigation status, outcome, durable-record, and tool chips use MetadataBadge for shared shell, sizing, tone, and outline behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5181,7 +5181,7 @@
|
||||
},
|
||||
"scopes": ["src/components/patrol"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["px-1.5 py-0.5", "text-[10px] font-medium rounded"],
|
||||
"allPatterns": ["px-1.5 py-0.5", "text-[10px] font-medium rounded-sm"],
|
||||
"legacyReason": "Retired migration debt. Patrol approval risk, approval state, and tool-call result chips use MetadataBadge for shared shell, sizing, tone, and whitespace behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5222,7 +5222,7 @@
|
||||
},
|
||||
"scopes": ["src/features/proxmox"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["rounded-sm px-1.5 py-0.5 text-[10px] font-semibold"],
|
||||
"allPatterns": ["rounded-xs px-1.5 py-0.5 text-[10px] font-semibold"],
|
||||
"legacyReason": "Retired migration debt. Proxmox backup source/state chips use MetadataBadge for shared shell, sizing, tone vocabulary, and whitespace behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
@@ -5456,7 +5456,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex items-center gap-1 rounded-md border border-border bg-surface p-1",
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-sm px-3 text-xs font-medium transition-colors"
|
||||
"inline-flex min-h-8 items-center gap-1.5 rounded-xs px-3 text-xs font-medium transition-colors"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Feature table view toggles must use FilterButtonGroup; local bordered segmented-view selector forks are not allowed.",
|
||||
"allowedPaths": [],
|
||||
@@ -5478,7 +5478,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-full border px-3 py-2 text-sm font-semibold transition",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-sm",
|
||||
"border-blue-500 bg-blue-600 text-white shadow-xs",
|
||||
"hover:border-blue-400 hover:text-blue-600"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Pressed/unpressed pill selectors belong to SelectablePillButton.",
|
||||
@@ -5550,7 +5550,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
"inline-flex h-8 min-h-11 min-w-11 items-center gap-1.5 rounded-sm border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Drawer header Assistant and context-copy actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -5572,7 +5572,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
"inline-flex h-8 w-8 min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Drawer header close and icon actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -5642,7 +5642,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded border border-border bg-surface px-2 text-muted transition-colors hover:bg-surface-hover hover:text-base-content",
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded-sm border border-border bg-surface px-2 text-muted transition-colors hover:bg-surface-hover hover:text-base-content",
|
||||
"fallback={<CopyIcon class=\"h-3.5 w-3.5\" />}"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Neutral copy-value icon buttons belong to CopyValueButton.",
|
||||
@@ -5686,7 +5686,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950",
|
||||
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950/25",
|
||||
"fallback={<CopyIcon class=\"h-3.5 w-3.5\" />}"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Accent copy-value icon buttons belong to CopyValueButton.",
|
||||
@@ -5708,7 +5708,7 @@
|
||||
"scopes": ["src/components", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"flex items-start gap-2 rounded bg-surface-alt px-2 py-1.5",
|
||||
"flex items-start gap-2 rounded-sm bg-surface-alt px-2 py-1.5",
|
||||
"break-all font-mono text-xs text-base-content"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Copyable code rows belong to CopyableCodeRow so code styling and copy behavior stay canonical.",
|
||||
@@ -6012,7 +6012,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"px-3 py-1.5 bg-green-600 hover:bg-green-700",
|
||||
"text-white text-xs font-medium rounded"
|
||||
"text-white text-xs font-medium rounded-sm"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Patrol approval execute actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6035,7 +6035,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"px-3 py-1.5 bg-amber-600 hover:bg-amber-700",
|
||||
"text-white text-xs font-medium rounded"
|
||||
"text-white text-xs font-medium rounded-sm"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Patrol approval warning actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6058,7 +6058,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"px-3 py-1.5 bg-blue-600 hover:bg-blue-700",
|
||||
"text-white text-xs font-medium rounded"
|
||||
"text-white text-xs font-medium rounded-sm"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Patrol Assistant handoff actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6103,7 +6103,7 @@
|
||||
"scopes": ["src/components/Settings", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900"
|
||||
"rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Positive outlined settings actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6124,7 +6124,7 @@
|
||||
},
|
||||
"scopes": ["src/components/Settings", "src/features", "src/pages"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["rounded-md px-3 py-2 text-sm font-medium text-emerald-900"],
|
||||
"allPatterns": ["rounded-md px-3 py-2 text-sm font-medium text-emerald-900/25"],
|
||||
"legacyReason": "Retired migration debt. Positive ghost settings actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
|
||||
@@ -6146,7 +6146,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-md px-2 py-1 text-xs font-medium text-emerald-700",
|
||||
"hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900"
|
||||
"hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-900/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Positive row actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
@@ -6255,7 +6255,7 @@
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"rounded-md px-2 py-1 text-xs font-medium text-red-600",
|
||||
"hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900"
|
||||
"hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-900/25"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Destructive row actions belong to the shared Button primitive family.",
|
||||
"allowedPaths": [],
|
||||
|
||||
@@ -136,9 +136,9 @@ export function sessionHasSettingsAccess(scopes: string[] | undefined): boolean
|
||||
}
|
||||
const NAV_TAB_ICON_CLASS = 'w-4 h-4 shrink-0';
|
||||
const AI_CHAT_MOBILE_LAUNCHER_BUTTON_CLASS =
|
||||
'group relative flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full bg-surface-hover text-blue-600 transition-colors hover:bg-border hover:text-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
'group relative flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-surface-hover text-blue-600 transition-colors hover:bg-border hover:text-blue-700 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
const AI_CHAT_DESKTOP_LAUNCHER_BUTTON_CLASS =
|
||||
'fixed right-0 top-1/2 z-40 flex min-h-9 min-w-10 -translate-y-1/2 items-center justify-center rounded-l-lg border border-r-0 border-border bg-surface px-2.5 py-2.5 text-blue-600 transition-colors duration-200 hover:bg-surface-hover hover:text-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
'fixed right-0 top-1/2 z-40 flex min-h-9 min-w-10 -translate-y-1/2 items-center justify-center rounded-l-lg border border-r-0 border-border bg-surface px-2.5 py-2.5 text-blue-600 transition-colors duration-200 hover:bg-surface-hover hover:text-blue-700 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-400 dark:hover:text-blue-300';
|
||||
|
||||
function getDesktopUtilityTabAriaLabel(tab: UtilityTab): string {
|
||||
const count = tab.count ?? 0;
|
||||
@@ -208,7 +208,7 @@ export function ConnectionStatusBadge(props: {
|
||||
aria-label={status().detail}
|
||||
>
|
||||
<Show when={showSpinner()}>
|
||||
<svg class="animate-spin h-3 w-3 flex-shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<svg class="animate-spin h-3 w-3 shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
@@ -225,7 +225,7 @@ export function ConnectionStatusBadge(props: {
|
||||
</svg>
|
||||
</Show>
|
||||
<Show when={!showSpinner()}>
|
||||
<span class={`h-2.5 w-2.5 rounded-full flex-shrink-0 ${indicatorClass()}`} />
|
||||
<span class={`h-2.5 w-2.5 rounded-full shrink-0 ${indicatorClass()}`} />
|
||||
</Show>
|
||||
<span
|
||||
class={`whitespace-nowrap overflow-hidden transition-all duration-500 ${
|
||||
@@ -470,7 +470,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
title={assistantPageContext().title}
|
||||
aria-label={assistantPageContext().ariaLabel}
|
||||
>
|
||||
<SparklesIcon class="h-5 w-5 flex-shrink-0" />
|
||||
<SparklesIcon class="h-5 w-5 shrink-0" />
|
||||
</button>
|
||||
);
|
||||
createEffect(() => {
|
||||
@@ -691,7 +691,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
|
||||
const className = () => {
|
||||
if (isActive()) {
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-sm font-semibold`;
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-xs font-semibold`;
|
||||
}
|
||||
if (needsSetup()) {
|
||||
return `${baseClasses} text-muted opacity-70 bg-base hover:bg-surface-hover`;
|
||||
@@ -718,7 +718,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<span class="hidden xs:inline-flex items-center gap-1">
|
||||
<span>{tab.label}</span>
|
||||
<Show when={tab.badge}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted bg-surface-hover rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted bg-surface-hover rounded-sm">
|
||||
{tab.badge}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -751,7 +751,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<div
|
||||
class={`header mb-1 flex flex-wrap items-center gap-1 sm:flex-nowrap sm:mb-3 sm:gap-2 ${
|
||||
kioskMode()
|
||||
? 'fixed top-0 left-0 right-0 z-50 justify-end bg-surface shadow-sm'
|
||||
? 'fixed top-0 left-0 right-0 z-50 justify-end bg-surface shadow-xs'
|
||||
: 'justify-between sm:grid sm:grid-cols-[1fr_auto_1fr] sm:items-center sm:gap-0'
|
||||
}`}
|
||||
style={
|
||||
@@ -797,7 +797,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<img
|
||||
src={logoDataUrl()}
|
||||
alt={customBrandName() ? `${customBrandName()} logo` : 'Custom logo'}
|
||||
class="max-h-8 max-w-[12rem] object-contain"
|
||||
class="max-h-8 max-w-48 object-contain"
|
||||
data-testid="custom-brand-logo"
|
||||
/>
|
||||
)}
|
||||
@@ -808,7 +808,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.versionInfo()?.channel === 'rc'}>
|
||||
<span class="text-xs px-1.5 py-0.5 bg-orange-700 text-white rounded font-bold">
|
||||
<span class="text-xs px-1.5 py-0.5 bg-orange-700 text-white rounded-sm font-bold">
|
||||
Preview
|
||||
</span>
|
||||
</Show>
|
||||
@@ -834,9 +834,9 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleKioskMode}
|
||||
class={`group relative flex h-9 w-9 items-center justify-center rounded-full text-xs transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10 ${
|
||||
class={`group relative flex h-9 w-9 items-center justify-center rounded-full text-xs transition focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10 ${
|
||||
kioskMode()
|
||||
? 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-300 dark:hover:bg-blue-800'
|
||||
? 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/25 dark:text-blue-300 dark:hover:bg-blue-800'
|
||||
: 'bg-surface-hover text-base-content hover:bg-border'
|
||||
}`}
|
||||
title={
|
||||
@@ -847,8 +847,8 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
aria-label={kioskMode() ? 'Exit kiosk mode' : 'Enter kiosk mode'}
|
||||
aria-pressed={kioskMode()}
|
||||
>
|
||||
<Show when={kioskMode()} fallback={<Maximize2Icon class="h-4 w-4 flex-shrink-0" />}>
|
||||
<Minimize2Icon class="h-4 w-4 flex-shrink-0" />
|
||||
<Show when={kioskMode()} fallback={<Maximize2Icon class="h-4 w-4 shrink-0" />}>
|
||||
<Minimize2Icon class="h-4 w-4 shrink-0" />
|
||||
</Show>
|
||||
</button>
|
||||
<Show when={props.proxyAuthInfo()?.username}>
|
||||
@@ -857,12 +857,12 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.handleLogout}
|
||||
class="group relative flex h-9 w-9 items-center justify-center rounded-full bg-surface-hover text-xs text-base-content transition hover:bg-border focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10"
|
||||
class="group relative flex h-9 w-9 items-center justify-center rounded-full bg-surface-hover text-xs text-base-content transition hover:bg-border focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 sm:h-10 sm:w-10"
|
||||
title="Logout"
|
||||
aria-label="Logout"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4 flex-shrink-0"
|
||||
class="h-4 w-4 shrink-0"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -878,7 +878,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<ConnectionStatusBadge connectionStatus={props.connectionStatus} class="flex-shrink-0" />
|
||||
<ConnectionStatusBadge connectionStatus={props.connectionStatus} class="shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -901,7 +901,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
|
||||
const className = () => {
|
||||
if (isActive()) {
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-sm font-semibold`;
|
||||
return `${baseClasses} bg-surface text-blue-600 dark:text-blue-400 border-border border-b border-b-surface shadow-xs font-semibold`;
|
||||
}
|
||||
return `${baseClasses} text-muted hover:text-base-content hover:bg-surface-hover`;
|
||||
};
|
||||
@@ -960,7 +960,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={tab.badge === 'pro' && !presentationPolicyHidesUpgradePrompts()}>
|
||||
<span class="ml-1.5 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900 rounded">
|
||||
<span class="ml-1.5 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900/25 rounded-sm">
|
||||
Pro
|
||||
</span>
|
||||
</Show>
|
||||
@@ -976,7 +976,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<main
|
||||
id="main"
|
||||
tabindex="-1"
|
||||
class="tab-content mb-1 block rounded-b rounded-tl rounded-tr bg-surface shadow sm:mb-2"
|
||||
class="tab-content mb-1 block rounded-b rounded-tl rounded-tr bg-surface shadow-sm sm:mb-2"
|
||||
>
|
||||
<div class="pulse-panel">
|
||||
<Suspense fallback={<div class="p-6 text-sm text-muted">Loading view...</div>}>
|
||||
@@ -1004,7 +1004,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
href="https://github.com/rcourtman/Pulse/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex min-h-8 items-center break-all rounded px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
class="inline-flex min-h-8 items-center break-all rounded-sm px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
>
|
||||
{props.versionInfo()?.version || 'loading...'}
|
||||
</a>
|
||||
@@ -1023,7 +1023,7 @@ export function AppLayout(props: AppLayoutProps) {
|
||||
<Show when={isPro()}>
|
||||
<a
|
||||
href={`mailto:support@pulserelay.pro?subject=${encodeURIComponent(`Support Request - Pulse ${props.versionInfo()?.version || ''}`)}`}
|
||||
class="inline-flex min-h-8 items-center rounded px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
class="inline-flex min-h-8 items-center rounded-sm px-1 py-1 text-blue-600 hover:underline dark:text-blue-400 sm:min-h-9"
|
||||
>
|
||||
Get Support
|
||||
</a>
|
||||
|
||||
@@ -639,7 +639,7 @@ describe('App architecture', () => {
|
||||
expect(appLayoutSource).toContain('animate-pulse-brand');
|
||||
expect(appLayoutSource).toContain('pulse-brand-wordmark');
|
||||
expect(appLayoutSource).toContain("'pb-safe-or-14 xl:pb-0'");
|
||||
expect(appStylesSource).toContain('.pb-safe-or-14');
|
||||
expect(appStylesSource).toContain('@utility pb-safe-or-14');
|
||||
expect(appStylesSource).toContain('.pulse-shell--full-width');
|
||||
expect(appStylesSource).toContain('.pulse-wide-data-surface.space-y-3');
|
||||
expect(appStylesSource).toContain('.filter-bar > div > div:first-child button');
|
||||
|
||||
@@ -721,26 +721,26 @@ export const investigationOutcomeLabels: Record<InvestigationOutcome, string> =
|
||||
*/
|
||||
export const investigationOutcomeColors: Record<InvestigationOutcome, string> = {
|
||||
resolved:
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300',
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300',
|
||||
fix_queued:
|
||||
'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-300',
|
||||
'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-300',
|
||||
fix_executed:
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300',
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300',
|
||||
fix_failed:
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900 dark:text-red-300',
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900/25 dark:text-red-300',
|
||||
fix_rejected:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
needs_attention:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
cannot_fix: 'border-slate-200 bg-slate-50 text-slate-600',
|
||||
timed_out:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
fix_verified:
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900 dark:text-green-300',
|
||||
'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/25 dark:text-green-300',
|
||||
fix_verification_failed:
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900 dark:text-red-300',
|
||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900/25 dark:text-red-300',
|
||||
fix_verification_unknown:
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-300',
|
||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -287,10 +287,10 @@ export const AICostDashboard: Component = () => {
|
||||
|
||||
return (
|
||||
<Card padding="none" class="overflow-hidden border border-border" border={false}>
|
||||
<div class="bg-blue-50 dark:bg-blue-900 px-4 py-4 border-b border-border sm:px-6">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/25 px-4 py-4 border-b border-border sm:px-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<div class="flex min-w-0 items-start gap-3 sm:flex-1 sm:items-center">
|
||||
<div class="shrink-0 p-2 bg-blue-100 dark:bg-blue-900 rounded-md">
|
||||
<div class="shrink-0 p-2 bg-blue-100 dark:bg-blue-900/25 rounded-md">
|
||||
<svg
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-300"
|
||||
fill="none"
|
||||
@@ -340,14 +340,14 @@ export const AICostDashboard: Component = () => {
|
||||
</Show>
|
||||
|
||||
<Show when={summary()?.truncated}>
|
||||
<div class="text-xs px-3 py-2 rounded border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900 text-blue-900 dark:text-blue-100">
|
||||
<div class="text-xs px-3 py-2 rounded-sm border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/25 text-blue-900/25 dark:text-blue-100">
|
||||
Showing the last {summary()?.effective_days} days due to a {summary()?.retention_days}
|
||||
-day retention window.
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={isOverBudget()}>
|
||||
<div class="text-xs px-3 py-2 rounded border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100">
|
||||
<div class="text-xs px-3 py-2 rounded-sm border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25 text-red-900/25 dark:text-red-100">
|
||||
Estimated spend ({formatUSD(estimatedTotalUSD() ?? 0)}) is above your budget (
|
||||
{formatUSD(budgetForRange() ?? 0)}).
|
||||
</div>
|
||||
@@ -357,14 +357,14 @@ export const AICostDashboard: Component = () => {
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
class="flex items-center justify-between gap-3 text-xs px-3 py-2 rounded border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-900 dark:text-amber-100"
|
||||
class="flex items-center justify-between gap-3 text-xs px-3 py-2 rounded-sm border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 text-amber-900/25 dark:text-amber-100"
|
||||
>
|
||||
<div class="truncate">Couldn’t refresh. Showing last loaded data. {loadError()}</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={() => loadSummary(days())}
|
||||
class={`shrink-0 px-2 py-1 rounded border border-amber-300 dark:border-amber-700 hover:bg-amber-100 dark:hover:bg-amber-900 ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`shrink-0 px-2 py-1 rounded-sm border border-amber-300 dark:border-amber-700 hover:bg-amber-100 dark:hover:bg-amber-900/25 ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
@@ -522,7 +522,7 @@ export const AICostDashboard: Component = () => {
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={() => downloadExport('csv')}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded-sm border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Export CSV
|
||||
</button>
|
||||
@@ -530,7 +530,7 @@ export const AICostDashboard: Component = () => {
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={() => downloadExport('json')}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded-sm border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Export JSON
|
||||
</button>
|
||||
@@ -538,7 +538,7 @@ export const AICostDashboard: Component = () => {
|
||||
type="button"
|
||||
disabled={loading()}
|
||||
onClick={resetHistory}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
class={`min-h-10 sm:min-h-9 px-2.5 py-2 text-sm rounded-sm border border-border hover:bg-surface-hover ${loading() ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{AI_COST_RESET_HISTORY_LABEL}
|
||||
</button>
|
||||
|
||||
@@ -26,11 +26,11 @@ const confidenceLabel = (level?: string) => {
|
||||
const confidenceClasses = (level?: string) => {
|
||||
switch (level) {
|
||||
case 'verified':
|
||||
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-200';
|
||||
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/25 dark:text-emerald-200';
|
||||
case 'partial':
|
||||
return 'bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-200';
|
||||
return 'bg-sky-100 text-sky-700 dark:bg-sky-900/25 dark:text-sky-200';
|
||||
case 'inferred':
|
||||
return 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-200';
|
||||
return 'bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-200';
|
||||
default:
|
||||
return 'bg-base-200 text-base-content';
|
||||
}
|
||||
@@ -51,18 +51,18 @@ const formatExpiry = (expiresAt?: string) => {
|
||||
|
||||
export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
return (
|
||||
<div class="rounded-md border border-amber-300 dark:border-amber-700 overflow-hidden shadow-sm">
|
||||
<div class="rounded-md border border-amber-300 dark:border-amber-700 overflow-hidden shadow-xs">
|
||||
{/* Header */}
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200 border-b border-amber-200 dark:border-amber-800">
|
||||
<div class="p-1 rounded bg-amber-100 dark:bg-amber-800">
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-amber-50 dark:bg-amber-900/25 text-amber-800 dark:text-amber-200 border-b border-amber-200 dark:border-amber-800">
|
||||
<div class="p-1 rounded-sm bg-amber-100 dark:bg-amber-800">
|
||||
<AlertTriangleIcon class="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<span class="font-semibold">Approval Required</span>
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded text-[10px] font-bold uppercase tracking-wider">
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded-sm text-[10px] font-bold uppercase tracking-wider">
|
||||
{riskLabel(props.approval.risk)}
|
||||
</span>
|
||||
<Show when={props.approval.runOnHost}>
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded text-[10px] font-bold uppercase tracking-wider">
|
||||
<span class="px-1.5 py-0.5 bg-amber-200 dark:bg-amber-800 rounded-sm text-[10px] font-bold uppercase tracking-wider">
|
||||
Agent
|
||||
</span>
|
||||
</Show>
|
||||
@@ -74,9 +74,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</div>
|
||||
|
||||
{/* Command */}
|
||||
<div class="px-3 py-3 bg-amber-50 dark:bg-amber-900">
|
||||
<div class="px-3 py-3 bg-amber-50 dark:bg-amber-900/25">
|
||||
<Show when={props.approval.description}>
|
||||
<p class="mb-3 text-xs leading-relaxed text-amber-900 dark:text-amber-100">
|
||||
<p class="mb-3 text-xs leading-relaxed text-amber-900/25 dark:text-amber-100">
|
||||
{props.approval.description}
|
||||
</p>
|
||||
</Show>
|
||||
@@ -100,7 +100,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 p-2 bg-surface rounded border border-amber-200 dark:border-amber-700">
|
||||
<div class="mb-3 p-2 bg-surface rounded-sm border border-amber-200 dark:border-amber-700">
|
||||
<code class="text-xs font-mono text-base-content break-all">
|
||||
{props.approval.command}
|
||||
</code>
|
||||
@@ -131,7 +131,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Approval
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.plan?.approval_policy || 'admin approval'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +139,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Blast radius
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.plan?.blast_radius || 'target resource state'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<span class="text-[11px] font-semibold uppercase text-amber-700 dark:text-amber-300">
|
||||
Preflight
|
||||
</span>
|
||||
<span class="px-1.5 py-0.5 rounded bg-base-200 text-[10px] font-bold uppercase text-base-content">
|
||||
<span class="px-1.5 py-0.5 rounded-sm bg-base-200 text-[10px] font-bold uppercase text-base-content">
|
||||
{props.approval.preflight?.dry_run_available ? 'Dry run' : 'No dry run'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -181,7 +181,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Target
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.preflight?.target}
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,7 +191,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="uppercase font-semibold text-amber-700 dark:text-amber-300">
|
||||
Intended change
|
||||
</div>
|
||||
<div class="text-base-content break-words">
|
||||
<div class="text-base-content wrap-break-word">
|
||||
{props.approval.preflight?.intended_change}
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,9 +215,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="text-[11px] font-semibold uppercase text-amber-700 dark:text-amber-300">
|
||||
Safety checks
|
||||
</div>
|
||||
<ul class="mt-1 space-y-1 text-[11px] text-base-content/80">
|
||||
<ul class="mt-1 space-y-1 text-[11px]">
|
||||
<For each={props.approval.preflight?.safety_checks || []}>
|
||||
{(item) => <li class="break-words">{item}</li>}
|
||||
{(item) => <li class="wrap-break-word">{item}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -228,9 +228,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
<div class="text-[11px] font-semibold uppercase text-amber-700 dark:text-amber-300">
|
||||
Verification
|
||||
</div>
|
||||
<ul class="mt-1 space-y-1 text-[11px] text-base-content/80">
|
||||
<ul class="mt-1 space-y-1 text-[11px]">
|
||||
<For each={props.approval.preflight?.verification_steps || []}>
|
||||
{(item) => <li class="break-words">{item}</li>}
|
||||
{(item) => <li class="wrap-break-word">{item}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -256,9 +256,9 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={(props.approval.contextConfidence?.evidence || []).length > 0}>
|
||||
<ul class="mt-1.5 space-y-1 text-[11px] text-base-content/80">
|
||||
<ul class="mt-1.5 space-y-1 text-[11px]">
|
||||
<For each={props.approval.contextConfidence?.evidence || []}>
|
||||
{(item) => <li class="break-words">{item}</li>}
|
||||
{(item) => <li class="wrap-break-word">{item}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
</Show>
|
||||
@@ -266,7 +266,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<Show when={props.approval.auditId || props.approval.plan?.plan_hash}>
|
||||
<div class="pt-2 border-t border-amber-200 dark:border-amber-700 text-[11px] text-base-content/80 break-all">
|
||||
<div class="pt-2 border-t border-amber-200 dark:border-amber-700 text-[11px] break-all">
|
||||
<Show when={props.approval.auditId}>
|
||||
<span>Audit {props.approval.auditId}</span>
|
||||
</Show>
|
||||
@@ -288,7 +288,7 @@ export const ApprovalCard: Component<ApprovalCardProps> = (props) => {
|
||||
class={`flex-1 px-3 py-2 text-xs font-semibold rounded-md transition-all ${
|
||||
props.approval.isExecuting
|
||||
? 'bg-green-400 text-white cursor-wait'
|
||||
: 'bg-green-500 hover:bg-green-600 text-white shadow-sm hover:shadow-sm'
|
||||
: 'bg-green-500 hover:bg-green-600 text-white shadow-xs hover:shadow-xs'
|
||||
}`}
|
||||
>
|
||||
<Show
|
||||
|
||||
@@ -112,7 +112,7 @@ export function AssistantCommandHelpDialog(props: AssistantCommandHelpDialogProp
|
||||
isOpen={true}
|
||||
onClose={props.onClose}
|
||||
layout="mobile-sheet"
|
||||
panelClass="max-w-[30rem] shadow-xl"
|
||||
panelClass="max-w-120 shadow-xl"
|
||||
ariaLabel={AI_CHAT_COMMAND_HELP_TITLE}
|
||||
>
|
||||
<>
|
||||
@@ -139,7 +139,7 @@ export function AssistantCommandHelpDialog(props: AssistantCommandHelpDialogProp
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="max-h-[25rem] overflow-y-auto p-2"
|
||||
class="max-h-100 overflow-y-auto p-2"
|
||||
role="listbox"
|
||||
aria-label={AI_CHAT_COMMAND_HELP_TITLE}
|
||||
>
|
||||
@@ -172,7 +172,7 @@ export function AssistantCommandHelpDialog(props: AssistantCommandHelpDialogProp
|
||||
role="option"
|
||||
aria-selected={item.index === selectedCommandIndex()}
|
||||
aria-disabled={command.disabled ? 'true' : undefined}
|
||||
class={`flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-left transition-colors focus:outline-none ${
|
||||
class={`flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-left transition-colors focus:outline-hidden ${
|
||||
command.disabled
|
||||
? 'cursor-help opacity-55'
|
||||
: 'hover:bg-surface-hover focus:bg-surface-hover'
|
||||
|
||||
@@ -286,7 +286,7 @@ export const ChatMessages: Component<ChatMessagesProps> = (props) => {
|
||||
{(prompt) => (
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left text-sm text-base-content transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left text-sm text-base-content transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
onClick={() => props.onSuggestedPrompt?.(prompt)}
|
||||
data-testid="assistant-suggested-prompt"
|
||||
>
|
||||
@@ -310,7 +310,7 @@ export const ChatMessages: Component<ChatMessagesProps> = (props) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-left transition-colors hover:border-blue-300 hover:bg-surface-alt focus:outline-hidden focus:ring-2 focus:ring-blue-500/30"
|
||||
onClick={() => props.onLoadSession?.(session.id)}
|
||||
aria-label={`Resume ${session.title || 'Untitled Assistant session'}`}
|
||||
>
|
||||
|
||||
@@ -184,7 +184,7 @@ export function MentionAutocomplete(props: MentionAutocompleteProps) {
|
||||
return (
|
||||
<Show when={props.visible && filteredResources().length > 0}>
|
||||
<div
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[400px] overflow-hidden rounded-md border border-border bg-surface shadow-sm"
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[400px] overflow-hidden rounded-md border border-border bg-surface shadow-xs"
|
||||
style={{
|
||||
bottom: `${props.position.top}px`,
|
||||
left: `${props.position.left}px`,
|
||||
@@ -247,11 +247,11 @@ export function MentionAutocomplete(props: MentionAutocompleteProps) {
|
||||
</For>
|
||||
</div>
|
||||
<div class="px-3 py-1.5 border-t border-border text-xs text-muted flex items-center gap-2">
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded text-[10px]">↑↓</span>
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded-sm text-[10px]">↑↓</span>
|
||||
navigate
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded text-[10px]">↵</span>
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded-sm text-[10px]">↵</span>
|
||||
select
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded text-[10px]">esc</span>
|
||||
<span class="px-1.5 py-0.5 bg-surface-hover rounded-sm text-[10px]">esc</span>
|
||||
close
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ interface MessageItemProps {
|
||||
}
|
||||
|
||||
const markdownClass =
|
||||
'text-sm prose prose-slate prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-p:my-2 prose-pre:bg-slate-900 prose-pre:text-slate-100 prose-pre:rounded-md prose-pre:text-xs prose-pre:border prose-pre:border-slate-800 prose-code:[overflow-wrap:anywhere] prose-code:text-blue-700 dark:prose-code:text-blue-300 prose-code:bg-blue-50 dark:prose-code:bg-blue-900 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:font-mono prose-code:text-[0.9em] prose-code:border prose-code:border-blue-100 dark:prose-code:border-blue-800 prose-code:before:content-none prose-code:after:content-none prose-headings:font-semibold prose-hr:border-slate-200 dark:prose-hr:border-slate-700 prose-ul:my-2 prose-ol:my-2 prose-li:my-1';
|
||||
'text-sm prose prose-slate prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-p:my-2 prose-pre:bg-slate-900 prose-pre:text-slate-100 prose-pre:rounded-md prose-pre:text-xs prose-pre:border prose-pre:border-slate-800 prose-code:wrap-anywhere prose-code:text-blue-700 dark:prose-code:text-blue-300 prose-code:bg-blue-50 dark:prose-code:bg-blue-900/25 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:font-mono prose-code:text-[0.9em] prose-code:border prose-code:border-blue-100 dark:prose-code:border-blue-800 prose-code:before:content-none prose-code:after:content-none prose-headings:font-semibold prose-hr:border-slate-200 dark:prose-hr:border-slate-700 prose-ul:my-2 prose-ol:my-2 prose-li:my-1';
|
||||
|
||||
const TEXT_RENDER_PACE_MS = 24;
|
||||
const TEXT_RENDER_SNAP = /[\s.,!?;:)\]]/;
|
||||
@@ -547,7 +547,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
title="Edit and resend"
|
||||
tone="outline"
|
||||
size="sm"
|
||||
class="mt-1 opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
class="mt-1 opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
>
|
||||
<PencilIcon class="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
@@ -560,13 +560,13 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
label={copyButtonLabel()}
|
||||
size="md"
|
||||
stopPropagation
|
||||
class="mt-1 opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
class="mt-1 opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
/>
|
||||
</Show>
|
||||
<div
|
||||
class={`min-w-0 px-4 py-2.5 rounded-md rounded-br-sm shadow-sm ${
|
||||
class={`min-w-0 px-4 py-2.5 rounded-md rounded-br-sm shadow-xs ${
|
||||
isQueuedUserMessage()
|
||||
? 'border border-blue-200 bg-blue-50 text-blue-950 dark:border-blue-900/60 dark:bg-blue-950/30 dark:text-blue-100'
|
||||
? 'border border-blue-200 bg-blue-50 text-blue-950/25 dark:border-blue-900/60 dark:bg-blue-950/30 dark:text-blue-100'
|
||||
: 'bg-blue-600 text-white'
|
||||
}`}
|
||||
>
|
||||
@@ -581,7 +581,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
<Show when={queuedModelRouteLabel()}>
|
||||
{(label) => (
|
||||
<span
|
||||
class="inline-flex max-w-[12rem] items-center rounded border border-blue-200 bg-white/80 px-1.5 py-0.5 text-[10px] font-semibold text-blue-800 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
class="inline-flex max-w-48 items-center rounded-sm border border-blue-200 bg-white/80 px-1.5 py-0.5 text-[10px] font-semibold text-blue-800 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
title={props.message.request?.model}
|
||||
>
|
||||
<span class="truncate">{label()}</span>
|
||||
@@ -617,7 +617,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
{/* Assistant message */}
|
||||
<Show when={!isUser()}>
|
||||
<div class="group flex w-full min-w-0 gap-3 px-1 py-2">
|
||||
<div class="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface-alt text-blue-600 shadow-sm dark:text-blue-400">
|
||||
<div class="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface-alt text-blue-600 shadow-xs dark:text-blue-400">
|
||||
<SparklesIcon class="h-3.5 w-3.5" />
|
||||
</div>
|
||||
|
||||
@@ -628,7 +628,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
</span>
|
||||
<Show when={messageModelLabel()}>
|
||||
<span
|
||||
class="max-w-[12rem] truncate rounded border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
class="max-w-48 truncate rounded-sm border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
title={props.message.model}
|
||||
>
|
||||
{messageModelLabel()}
|
||||
@@ -636,7 +636,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
</Show>
|
||||
<Show when={messageDurationLabel()}>
|
||||
<span
|
||||
class="inline-flex shrink-0 items-center gap-1 rounded border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
class="inline-flex shrink-0 items-center gap-1 rounded-sm border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[10px] font-medium text-muted"
|
||||
title="Turn duration"
|
||||
aria-label={`Turn duration ${messageDurationLabel()}`}
|
||||
>
|
||||
@@ -669,7 +669,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
label={copyButtonLabel()}
|
||||
size="md"
|
||||
stopPropagation
|
||||
class="ml-auto opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
class="ml-auto opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={props.onRegenerate}>
|
||||
@@ -679,7 +679,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
title="Regenerate response"
|
||||
tone="outline"
|
||||
size="sm"
|
||||
class={`${canCopy() ? '' : 'ml-auto '}opacity-0 shadow-sm transition-opacity focus:opacity-100 group-hover:opacity-100`}
|
||||
class={`${canCopy() ? '' : 'ml-auto '}opacity-0 shadow-xs transition-opacity focus:opacity-100 group-hover:opacity-100`}
|
||||
>
|
||||
<RotateCcwIcon class="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
@@ -931,7 +931,7 @@ export const MessageItem: Component<MessageItemProps> = (props) => {
|
||||
}
|
||||
aria-label={modelRouteRecoveryButtonLabel()}
|
||||
title={alternative().label}
|
||||
class="inline-flex max-w-[14rem] items-center gap-1.5 rounded-md border border-red-300 bg-white/80 px-2 py-1 text-xs font-medium text-red-700 transition-colors hover:bg-red-100 dark:border-red-800 dark:bg-red-950/20 dark:text-red-300 dark:hover:bg-red-900/40"
|
||||
class="inline-flex max-w-56 items-center gap-1.5 rounded-md border border-red-300 bg-white/80 px-2 py-1 text-xs font-medium text-red-700 transition-colors hover:bg-red-100 dark:border-red-800 dark:bg-red-950/20 dark:text-red-300 dark:hover:bg-red-900/40"
|
||||
>
|
||||
<CpuIcon class="h-3.5 w-3.5" />
|
||||
<span class="truncate">{modelRouteRecoveryButtonLabel()}</span>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const ModelSelector: Component<ModelSelectorProps> = (props) => {
|
||||
onManageProviders={props.onManageProviders}
|
||||
manageProvidersLabel="Open Assistant provider settings"
|
||||
align="left"
|
||||
buttonClass="flex flex-shrink-0 items-center gap-1.5 rounded-md border border-border bg-surface px-2.5 py-1.5 text-[11px] text-muted transition-colors hover:border-border hover:text-base-content"
|
||||
buttonClass="flex shrink-0 items-center gap-1.5 rounded-md border border-border bg-surface px-2.5 py-1.5 text-[11px] text-muted transition-colors hover:border-border hover:text-base-content"
|
||||
buttonLabelClass="max-w-[90px] truncate font-medium sm:max-w-[180px]"
|
||||
dropdownClass="w-80 max-w-[calc(100vw-2rem)]"
|
||||
/>
|
||||
|
||||
@@ -40,10 +40,10 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="rounded-md border border-blue-300 dark:border-blue-700 overflow-hidden shadow-sm">
|
||||
<div class="rounded-md border border-blue-300 dark:border-blue-700 overflow-hidden shadow-xs">
|
||||
{/* Header */}
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-blue-50 dark:bg-blue-900 text-blue-800 dark:text-blue-200 border-b border-blue-200 dark:border-blue-800">
|
||||
<div class="p-1 rounded bg-blue-100 dark:bg-blue-800">
|
||||
<div class="px-3 py-2 text-xs font-medium flex items-center gap-2 bg-blue-50 dark:bg-blue-900/25 text-blue-800 dark:text-blue-200 border-b border-blue-200 dark:border-blue-800">
|
||||
<div class="p-1 rounded-sm bg-blue-100 dark:bg-blue-800">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -57,7 +57,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
</div>
|
||||
|
||||
{/* Questions */}
|
||||
<div class="px-3 py-3 bg-blue-50 dark:bg-blue-900 space-y-4">
|
||||
<div class="px-3 py-3 bg-blue-50 dark:bg-blue-900/25 space-y-4">
|
||||
<For each={props.question.questions}>
|
||||
{(q) => (
|
||||
<div class="space-y-2">
|
||||
@@ -71,7 +71,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
type="text"
|
||||
value={answers()[q.id] || ''}
|
||||
onInput={(e) => handleInputChange(q.id, e.currentTarget.value)}
|
||||
class="w-full px-3 py-2 text-sm border border-blue-200 dark:border-blue-700 rounded-md bg-surface text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="w-full px-3 py-2 text-sm border border-blue-200 dark:border-blue-700 rounded-md bg-surface text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500"
|
||||
placeholder={AI_CHAT_QUESTION_CARD_PLACEHOLDER}
|
||||
disabled={props.question.isAnswering}
|
||||
/>
|
||||
@@ -88,7 +88,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
class={`w-full px-3 py-2 text-sm text-left rounded-md border transition-colors ${
|
||||
answers()[q.id] === option.value
|
||||
? 'bg-blue-100 dark:bg-blue-800 border-blue-400 dark:border-blue-600 text-blue-800 dark:text-blue-200'
|
||||
: 'bg-surface border-blue-200 dark:border-blue-700 text-base-content hover:bg-blue-50 dark:hover:bg-blue-900'
|
||||
: 'bg-surface border-blue-200 dark:border-blue-700 text-base-content hover:bg-blue-50 dark:hover:bg-blue-900/25'
|
||||
} ${props.question.isAnswering ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
@@ -117,7 +117,7 @@ export const QuestionCard: Component<QuestionCardProps> = (props) => {
|
||||
? 'bg-blue-400 text-white cursor-wait'
|
||||
: !isValid()
|
||||
? 'bg-surface-hover text-muted cursor-not-allowed'
|
||||
: 'bg-blue-500 hover:bg-blue-600 text-white shadow-sm hover:shadow-sm'
|
||||
: 'bg-blue-500 hover:bg-blue-600 text-white shadow-xs hover:shadow-xs'
|
||||
}`}
|
||||
>
|
||||
<Show
|
||||
|
||||
@@ -166,7 +166,7 @@ export function SlashCommandAutocomplete(props: SlashCommandAutocompleteProps) {
|
||||
return (
|
||||
<Show when={props.visible}>
|
||||
<div
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[420px] overflow-hidden rounded-md border border-border bg-surface shadow-sm"
|
||||
class="absolute z-50 w-[calc(100vw-36px)] min-w-[280px] max-w-[420px] overflow-hidden rounded-md border border-border bg-surface shadow-xs"
|
||||
style={{
|
||||
bottom: `${props.position.top}px`,
|
||||
left: `${props.position.left}px`,
|
||||
|
||||
@@ -63,7 +63,7 @@ export const ThinkingBlock: Component<ThinkingBlockProps> = (props) => {
|
||||
/>
|
||||
<span class="min-w-0 truncate">
|
||||
{statusText()}
|
||||
<span class="text-muted/80">
|
||||
<span class="">
|
||||
{durationLabel()
|
||||
? props.isStreaming
|
||||
? ` (${durationLabel()})`
|
||||
|
||||
@@ -156,7 +156,7 @@ const ToolInputSummary: Component<ToolInputSummaryProps> = (props) => {
|
||||
const isShellSummary = createMemo(() => props.summary.trim().startsWith('$ '));
|
||||
const className = createMemo(
|
||||
() =>
|
||||
`mt-1 block whitespace-pre-wrap break-words leading-5 text-base-content ${
|
||||
`mt-1 block whitespace-pre-wrap wrap-break-word leading-5 text-base-content ${
|
||||
isShellSummary() ? 'font-mono text-[11px]' : 'text-[12px] font-medium'
|
||||
}`,
|
||||
);
|
||||
@@ -210,7 +210,7 @@ const VisibleToolCommandPreview: Component<ToolCommandPreviewProps> = (props) =>
|
||||
<code
|
||||
data-testid="tool-command-preview"
|
||||
aria-label="Tool command"
|
||||
class="min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-[11px] leading-5 text-muted"
|
||||
class="min-w-0 flex-1 whitespace-pre-wrap wrap-break-word font-mono text-[11px] leading-5 text-muted"
|
||||
>
|
||||
{props.preview}
|
||||
</code>
|
||||
@@ -254,7 +254,7 @@ const detailCopyTarget = (kind: ToolDetailKind) => {
|
||||
};
|
||||
|
||||
const detailPreClass = (kind: ToolDetailKind) =>
|
||||
`${kind === 'output' ? 'max-h-72' : 'max-h-36'} overflow-auto rounded bg-surface-alt p-2 font-mono text-[10px] leading-5 text-muted whitespace-pre-wrap break-words`;
|
||||
`${kind === 'output' ? 'max-h-72' : 'max-h-36'} overflow-auto rounded-sm bg-surface-alt p-2 font-mono text-[10px] leading-5 text-muted whitespace-pre-wrap wrap-break-word`;
|
||||
|
||||
const ToolDetailsPanel: Component<ToolDetailsPanelProps> = (props) => {
|
||||
const [copiedDetail, setCopiedDetail] = createSignal<ToolDetailKind | null>(null);
|
||||
@@ -450,7 +450,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
</Show>
|
||||
<Show when={hiddenOutputBadgeSummary()}>
|
||||
<span
|
||||
class="shrink-0 rounded border border-border-subtle bg-surface px-1.5 py-0.5 text-[9px] font-medium"
|
||||
class="shrink-0 rounded-sm border border-border-subtle bg-surface px-1.5 py-0.5 text-[9px] font-medium"
|
||||
title="Open the completed turn to inspect tool output"
|
||||
aria-label={`Tool output available: ${hiddenOutputBadgeSummary()}`}
|
||||
>
|
||||
@@ -461,14 +461,14 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
}
|
||||
>
|
||||
<div
|
||||
class="my-2 overflow-hidden rounded-md border border-border-subtle bg-surface text-[11px] shadow-sm"
|
||||
class="my-2 overflow-hidden rounded-md border border-border-subtle bg-surface text-[11px] shadow-xs"
|
||||
role={settlingFastCompletion() ? 'status' : undefined}
|
||||
aria-label={settlingFastCompletion() ? 'Assistant tool running' : undefined}
|
||||
>
|
||||
<div
|
||||
class={`flex min-w-0 items-start gap-2 px-2.5 py-2 ${
|
||||
hasDetails()
|
||||
? 'cursor-pointer transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:ring-inset'
|
||||
? 'cursor-pointer transition-colors hover:bg-surface-hover focus:outline-hidden focus:ring-2 focus:ring-blue-500/30 focus:ring-inset'
|
||||
: ''
|
||||
}`}
|
||||
role={hasDetails() ? 'button' : undefined}
|
||||
@@ -512,7 +512,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
{toolLabel()}
|
||||
</span>
|
||||
<span
|
||||
class={`shrink-0 rounded border px-1.5 py-0.5 text-[9px] font-medium ${statusPillClass()}`}
|
||||
class={`shrink-0 rounded-sm border px-1.5 py-0.5 text-[9px] font-medium ${statusPillClass()}`}
|
||||
>
|
||||
{statusLabel()}
|
||||
</span>
|
||||
@@ -536,7 +536,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
</Show>
|
||||
<Show when={hiddenOutputBadgeSummary()}>
|
||||
<span
|
||||
class="shrink-0 rounded border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[9px] font-medium text-muted"
|
||||
class="shrink-0 rounded-sm border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[9px] font-medium text-muted"
|
||||
title="Open tool details to inspect output"
|
||||
aria-label={`Tool output available: ${hiddenOutputBadgeSummary()}`}
|
||||
>
|
||||
@@ -570,7 +570,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
Output preview
|
||||
</div>
|
||||
<pre
|
||||
class="px-3 pb-2 pt-1 font-mono text-[11px] leading-5 text-base-content whitespace-pre-wrap break-words"
|
||||
class="px-3 pb-2 pt-1 font-mono text-[11px] leading-5 text-base-content whitespace-pre-wrap wrap-break-word"
|
||||
aria-label="Tool output preview"
|
||||
>
|
||||
{outputPreview()}
|
||||
@@ -676,7 +676,7 @@ export const PendingToolBlock: Component<PendingToolBlockProps> = (props) => {
|
||||
<div
|
||||
class={`px-2.5 py-2 ${
|
||||
hasDetails()
|
||||
? 'cursor-pointer transition-colors hover:bg-blue-100/60 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:ring-inset dark:hover:bg-blue-950/30'
|
||||
? 'cursor-pointer transition-colors hover:bg-blue-100/60 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30 focus:ring-inset dark:hover:bg-blue-950/30'
|
||||
: ''
|
||||
}`}
|
||||
role={hasDetails() ? 'button' : undefined}
|
||||
@@ -731,8 +731,8 @@ export const PendingToolBlock: Component<PendingToolBlockProps> = (props) => {
|
||||
</div>
|
||||
|
||||
<Show when={progressText()}>
|
||||
<div class="mt-1 min-w-0 pl-[calc(0.875rem+0.5rem)] text-[10px] leading-snug text-muted">
|
||||
<span class="block whitespace-pre-wrap break-words" title={progressText()}>
|
||||
<div class="mt-1 min-w-0 pl-5.5 text-[10px] leading-snug text-muted">
|
||||
<span class="block whitespace-pre-wrap wrap-break-word" title={progressText()}>
|
||||
{progressText()}
|
||||
</span>
|
||||
</div>
|
||||
@@ -799,7 +799,7 @@ export const ToolCancellationBlock: Component<ToolCancellationBlockProps> = (pro
|
||||
<div
|
||||
class={`flex min-w-0 items-start gap-2 px-2.5 py-2 ${
|
||||
hasDetails()
|
||||
? 'cursor-pointer transition-colors hover:bg-amber-100/60 focus:outline-none focus:ring-2 focus:ring-amber-500/30 focus:ring-inset dark:hover:bg-amber-950/30'
|
||||
? 'cursor-pointer transition-colors hover:bg-amber-100/60 focus:outline-hidden focus:ring-2 focus:ring-amber-500/30 focus:ring-inset dark:hover:bg-amber-950/30'
|
||||
: ''
|
||||
}`}
|
||||
role={hasDetails() ? 'button' : undefined}
|
||||
@@ -822,7 +822,7 @@ export const ToolCancellationBlock: Component<ToolCancellationBlockProps> = (pro
|
||||
<span class="shrink-0 font-mono text-[9px] font-semibold uppercase tracking-wider text-muted">
|
||||
{toolLabel()}
|
||||
</span>
|
||||
<span class="shrink-0 rounded border border-amber-200 bg-amber-100 px-1.5 py-0.5 text-[9px] font-medium text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-200">
|
||||
<span class="shrink-0 rounded-sm border border-amber-200 bg-amber-100 px-1.5 py-0.5 text-[9px] font-medium text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-200">
|
||||
skipped
|
||||
</span>
|
||||
<Show when={hasDetails()}>
|
||||
@@ -839,7 +839,7 @@ export const ToolCancellationBlock: Component<ToolCancellationBlockProps> = (pro
|
||||
<ToolCommandPreview preview={commandPreview()} />
|
||||
</Show>
|
||||
<div class="mt-1 min-w-0 text-[10px] leading-snug text-muted">
|
||||
<span class="block whitespace-pre-wrap break-words">{reason()}</span>
|
||||
<span class="block whitespace-pre-wrap wrap-break-word">{reason()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -879,7 +879,7 @@ export const PendingToolsList: Component<PendingToolsListProps> = (props) => {
|
||||
<Show when={shouldCollapse() && !expanded()}>
|
||||
<button
|
||||
onClick={() => setExpanded(true)}
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:text-base-content hover:bg-surface-hover rounded text-center font-medium"
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:text-base-content hover:bg-surface-hover rounded-sm text-center font-medium"
|
||||
>
|
||||
+ {hiddenCount()} more tools running...
|
||||
</button>
|
||||
@@ -920,7 +920,7 @@ export const ToolExecutionsList: Component<ToolExecutionsListProps> = (props) =>
|
||||
<Show when={shouldCollapse() && !showAll()}>
|
||||
<button
|
||||
onClick={() => setShowAll(true)}
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:bg-surface-hover rounded text-center font-medium"
|
||||
class="w-full mt-0.5 py-1 text-[10px] text-muted hover:bg-surface-hover rounded-sm text-center font-medium"
|
||||
>
|
||||
+ {hiddenCount()} more tools ({stats().success} ✓ / {stats().failed} ✗)
|
||||
</button>
|
||||
|
||||
@@ -665,16 +665,16 @@ describe('AIChat', () => {
|
||||
);
|
||||
expect(aiChatSource).toContain('<ActionIconButton');
|
||||
for (const retiredActionIconShell of [
|
||||
'flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted',
|
||||
'flex-shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors',
|
||||
'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'rounded p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900 dark:hover:text-blue-300',
|
||||
'order-2 flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-none',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900 dark:text-amber-200 dark:hover:bg-amber-900',
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-sm transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45',
|
||||
'flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30',
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-surface disabled:hover:text-muted',
|
||||
'shrink-0 p-2 hover:text-base-content rounded-md hover:bg-surface-hover transition-colors',
|
||||
'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 disabled:cursor-wait disabled:opacity-70 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'rounded-sm p-1 text-muted opacity-0 transition-opacity hover:bg-blue-100 hover:text-blue-600 focus:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 dark:hover:bg-blue-900/25 dark:hover:text-blue-300',
|
||||
'order-2 flex h-9 w-9 shrink-0 items-center justify-center rounded-md hover:text-base-content hover:bg-surface-hover transition-colors sm:order-0',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md border border-amber-200 bg-surface text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:border-amber-800 dark:bg-amber-950/60 dark:text-amber-200 dark:hover:bg-amber-900/25',
|
||||
'flex h-7 w-7 items-center justify-center rounded-md text-amber-700 transition-colors hover:bg-amber-100 hover:text-amber-900/25 dark:text-amber-200 dark:hover:bg-amber-900/25',
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 dark:text-blue-200 dark:hover:bg-blue-900/60',
|
||||
'flex h-9 w-9 items-center justify-center rounded-md bg-blue-600 text-white shadow-xs transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-45',
|
||||
'flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30',
|
||||
'disabled:opacity-45',
|
||||
]) {
|
||||
expect(aiChatSource).not.toContain(retiredActionIconShell);
|
||||
@@ -2040,12 +2040,12 @@ describe('AIChat', () => {
|
||||
const panel = screen.getByText('Pulse Assistant').closest('[data-layout-mode]');
|
||||
expect(panel).toHaveAttribute('data-layout-mode', 'overlay');
|
||||
expect(panel).toHaveClass('top-0');
|
||||
expect(panel).toHaveClass('bottom-[var(--pulse-mobile-nav-height)]');
|
||||
expect(panel).toHaveClass('bottom-(--pulse-mobile-nav-height)');
|
||||
expect(panel).toHaveClass('h-auto');
|
||||
expect(panel).not.toHaveClass('inset-y-0');
|
||||
expect(panel).not.toHaveClass('h-full');
|
||||
const backdrop = screen.getByRole('button', { name: 'Close Pulse Assistant backdrop' });
|
||||
expect(backdrop).toHaveClass('bottom-[var(--pulse-mobile-nav-height)]');
|
||||
expect(backdrop).toHaveClass('bottom-(--pulse-mobile-nav-height)');
|
||||
expect(backdrop).not.toHaveClass('inset-0');
|
||||
expect(screen.queryByTitle('Collapse Pulse Assistant')).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Close Pulse Assistant')).toBeInTheDocument();
|
||||
|
||||
@@ -134,10 +134,10 @@ describe('MessageItem', () => {
|
||||
expect(messageItemSource).not.toContain('lucide-solid/icons/copy');
|
||||
expect(messageItemSource).not.toContain("lucide-solid/icons/check';");
|
||||
expect(messageItemSource).not.toContain(
|
||||
'mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-sm transition-opacity hover:text-base-content',
|
||||
'mt-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border-subtle bg-surface text-muted opacity-0 shadow-xs transition-opacity hover:text-base-content',
|
||||
);
|
||||
expect(messageItemSource).not.toContain(
|
||||
'inline-flex h-5 w-5 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950 focus:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500/30',
|
||||
'inline-flex h-5 w-5 items-center justify-center rounded-sm text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-950/25 focus:bg-blue-100 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('ToolExecutionBlock', () => {
|
||||
expect(toolExecutionBlockSource).not.toContain('lucide-solid/icons/copy');
|
||||
expect(toolExecutionBlockSource).not.toContain("lucide-solid/icons/check';");
|
||||
expect(toolExecutionBlockSource).not.toContain(
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30',
|
||||
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-sm border border-border-subtle bg-surface text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus:ring-2 focus:ring-blue-500/30',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -432,7 +432,7 @@ describe('ToolExecutionBlock', () => {
|
||||
.closest('[data-testid="tool-input-summary"]');
|
||||
expect(summary).toBeInTheDocument();
|
||||
expect(summary?.className).toContain('whitespace-pre-wrap');
|
||||
expect(summary?.className).toContain('break-words');
|
||||
expect(summary?.className).toContain('wrap-break-word');
|
||||
expect(summary?.className).not.toContain('truncate');
|
||||
});
|
||||
|
||||
@@ -755,7 +755,7 @@ describe('ToolExecutionBlock', () => {
|
||||
expect(detailBlocks.length).toBeGreaterThanOrEqual(2);
|
||||
for (const block of detailBlocks) {
|
||||
expect(block.className).toContain('whitespace-pre-wrap');
|
||||
expect(block.className).toContain('break-words');
|
||||
expect(block.className).toContain('wrap-break-word');
|
||||
expect(block.className).not.toContain('break-all');
|
||||
}
|
||||
});
|
||||
@@ -1022,7 +1022,7 @@ describe('PendingToolBlock', () => {
|
||||
.closest('[data-testid="tool-input-summary"]');
|
||||
expect(summary).toBeInTheDocument();
|
||||
expect(summary?.className).toContain('whitespace-pre-wrap');
|
||||
expect(summary?.className).toContain('break-words');
|
||||
expect(summary?.className).toContain('wrap-break-word');
|
||||
expect(summary?.className).not.toContain('truncate');
|
||||
});
|
||||
|
||||
@@ -1142,7 +1142,7 @@ describe('PendingToolBlock', () => {
|
||||
expect(progress).toBeInTheDocument();
|
||||
expect(progress).toHaveAttribute('title', 'Running command.');
|
||||
expect(progress.className).not.toContain('hidden');
|
||||
expect(progress.className).toContain('break-words');
|
||||
expect(progress.className).toContain('wrap-break-word');
|
||||
expect(progress.className).not.toContain('truncate');
|
||||
});
|
||||
|
||||
|
||||
@@ -2275,14 +2275,14 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
onCleanup(stopPanelResize);
|
||||
const rootClassName = createMemo(() => {
|
||||
if (isOverlayLayout()) {
|
||||
return `fixed top-0 right-0 bottom-[var(--pulse-mobile-nav-height)] z-50 flex h-auto w-full flex-col bg-surface transition-transform duration-300 sm:w-[560px] sm:max-w-[calc(100vw-1rem)] ${
|
||||
return `fixed top-0 right-0 bottom-(--pulse-mobile-nav-height) z-50 flex h-auto w-full flex-col bg-surface transition-transform duration-300 sm:w-[560px] sm:max-w-[calc(100vw-1rem)] ${
|
||||
isOpen()
|
||||
? 'translate-x-0 overflow-visible border-l border-border shadow-2xl'
|
||||
: 'translate-x-full overflow-hidden border-l-0'
|
||||
}`;
|
||||
}
|
||||
|
||||
return `relative flex h-full flex-shrink-0 flex-col bg-surface transition-all duration-300 ${
|
||||
return `relative flex h-full shrink-0 flex-col bg-surface transition-all duration-300 ${
|
||||
isOpen()
|
||||
? 'w-full overflow-visible border-l border-border sm:w-[560px]'
|
||||
: 'w-0 overflow-hidden border-l-0'
|
||||
@@ -2758,7 +2758,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
const currentStatusKind = createMemo(() => currentStatus()?.type);
|
||||
const currentStatusRowClass = createMemo(() => {
|
||||
if (currentStatusKind() === 'retrying') {
|
||||
return 'bg-amber-50/80 text-amber-900 dark:bg-amber-950/25 dark:text-amber-100';
|
||||
return 'bg-amber-50/80 text-amber-900/25 dark:bg-amber-950/25 dark:text-amber-100';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
@@ -4274,7 +4274,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={isOpen() && isOverlayLayout()}>
|
||||
<button
|
||||
type="button"
|
||||
class="fixed inset-x-0 top-0 bottom-[var(--pulse-mobile-nav-height)] z-40 bg-slate-950/45 backdrop-blur-[1px]"
|
||||
class="fixed inset-x-0 top-0 bottom-(--pulse-mobile-nav-height) z-40 bg-slate-950/45 backdrop-blur-[1px]"
|
||||
onClick={props.onClose}
|
||||
aria-label="Close Pulse Assistant backdrop"
|
||||
/>
|
||||
@@ -4287,7 +4287,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={isOpen()}>
|
||||
{/* Drag handle to resize the panel (desktop widths only) */}
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 z-50 hidden w-1.5 cursor-col-resize touch-none hover:bg-blue-400/40 focus:bg-blue-400/60 focus:outline-none sm:block"
|
||||
class="absolute inset-y-0 left-0 z-50 hidden w-1.5 cursor-col-resize touch-none hover:bg-blue-400/40 focus:bg-blue-400/60 focus:outline-hidden sm:block"
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize Assistant panel"
|
||||
@@ -4308,7 +4308,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
aria-label={AI_CHAT_COLLAPSE_TITLE}
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 flex-shrink-0"
|
||||
class="h-5 w-5 shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -4336,14 +4336,14 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="order-3 flex w-full min-w-0 flex-wrap items-center gap-1.5 sm:order-none sm:w-auto sm:flex-none"
|
||||
class="order-3 flex w-full min-w-0 flex-wrap items-center gap-1.5 sm:order-0 sm:w-auto sm:flex-none"
|
||||
data-testid="assistant-header-actions"
|
||||
>
|
||||
{/* New chat */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleNewConversation}
|
||||
class="flex flex-shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] text-muted hover:text-base-content rounded-md border border-border hover:border-border bg-surface transition-colors"
|
||||
class="flex shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] text-muted hover:text-base-content rounded-md border border-border hover:border-border bg-surface transition-colors"
|
||||
title={AI_CHAT_NEW_SESSION_BUTTON_TITLE}
|
||||
aria-label={AI_CHAT_NEW_SESSION_BUTTON_TITLE}
|
||||
>
|
||||
@@ -4507,7 +4507,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
|
||||
<Show when={showSessions()}>
|
||||
<div
|
||||
class="fixed w-[min(20rem,calc(100vw-1rem))] bg-surface rounded-md shadow-sm border border-border z-[9999] overflow-hidden"
|
||||
class="fixed w-[min(20rem,calc(100vw-1rem))] bg-surface rounded-md shadow-xs border border-border z-9999 overflow-hidden"
|
||||
role="dialog"
|
||||
aria-label={AI_CHAT_SESSION_MENU_TITLE}
|
||||
style={{
|
||||
@@ -4519,7 +4519,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleNewConversation}
|
||||
class="w-full px-3 py-2.5 text-left text-sm flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900 border-b border-border"
|
||||
class="w-full px-3 py-2.5 text-left text-sm flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/25 border-b border-border"
|
||||
aria-label={AI_CHAT_NEW_SESSION_MENU_ARIA_LABEL}
|
||||
>
|
||||
<PlusIcon class="h-4 w-4" aria-hidden="true" />
|
||||
@@ -4567,7 +4567,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<For each={sessionPickerSections()}>
|
||||
{(section) => (
|
||||
<>
|
||||
<div class="sticky top-0 z-10 border-b border-border/60 bg-surface-alt px-3 py-1.5 text-[11px] font-semibold text-muted">
|
||||
<div class="sticky top-0 z-10 border-b bg-surface-alt px-3 py-1.5 text-[11px] font-semibold text-muted">
|
||||
{section.title}
|
||||
</div>
|
||||
<For each={section.sessions}>
|
||||
@@ -4575,7 +4575,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<div
|
||||
class={`group relative flex items-start gap-2 px-3 py-2.5 hover:bg-surface-hover focus-within:bg-surface-hover ${
|
||||
isSessionCurrent(session.id)
|
||||
? 'bg-blue-50 dark:bg-blue-900'
|
||||
? 'bg-blue-50 dark:bg-blue-900/25'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
@@ -4590,7 +4590,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
role="option"
|
||||
aria-selected={isSessionCurrent(session.id)}
|
||||
aria-label={getSessionPickerOptionLabel(session)}
|
||||
class="min-w-0 flex-1 text-left focus:outline-none"
|
||||
class="min-w-0 flex-1 text-left focus:outline-hidden"
|
||||
onClick={() => handleLoadSession(session.id)}
|
||||
onKeyDown={(event) =>
|
||||
handleSessionOptionKeyDown(event, session.id)
|
||||
@@ -4601,7 +4601,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
{session.title || 'Untitled'}
|
||||
</div>
|
||||
<Show when={isSessionWorking(session.id)}>
|
||||
<span class="inline-flex shrink-0 items-center gap-1 rounded border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
<span class="inline-flex shrink-0 items-center gap-1 rounded-sm border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
<LoaderCircleIcon class="h-3 w-3 animate-spin" />
|
||||
Working
|
||||
</span>
|
||||
@@ -4612,7 +4612,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
!isSessionWorking(session.id)
|
||||
}
|
||||
>
|
||||
<span class="shrink-0 rounded border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
<span class="shrink-0 rounded-sm border border-blue-200 bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-blue-700 dark:border-blue-800 dark:bg-blue-950/60 dark:text-blue-200">
|
||||
Current
|
||||
</span>
|
||||
</Show>
|
||||
@@ -4635,15 +4635,15 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={session.handoff_summary}>
|
||||
{(summary) => (
|
||||
<div class="mt-1 flex max-w-full flex-wrap gap-1.5">
|
||||
<span class="rounded border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200">
|
||||
<span class="rounded-sm border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:border-blue-800 dark:bg-blue-950/25 dark:text-blue-200">
|
||||
{getSessionHandoffSourceLabel(summary())}
|
||||
</span>
|
||||
<span class="rounded border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
<span class="rounded-sm border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
{getSessionHandoffBadgeLabel(summary())}
|
||||
</span>
|
||||
<Show when={formatSessionHandoffStatus(summary())}>
|
||||
{(status) => (
|
||||
<span class="max-w-full truncate rounded border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
<span class="max-w-full truncate rounded-sm border border-border bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
{status()}
|
||||
</span>
|
||||
)}
|
||||
@@ -4672,7 +4672,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
maxLength={AI_CHAT_SESSION_TITLE_MAX_LENGTH}
|
||||
disabled={sessionRenameSaving()}
|
||||
aria-label={`New title for ${session.title || 'Untitled'}`}
|
||||
class="min-w-0 flex-1 rounded border border-blue-300 bg-surface px-2 py-1 text-sm font-medium text-base-content outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 disabled:opacity-70 dark:border-blue-800"
|
||||
class="min-w-0 flex-1 rounded-sm border border-blue-300 bg-surface px-2 py-1 text-sm font-medium text-base-content outline-hidden focus:border-blue-500 focus:ring-1 focus:ring-blue-500 disabled:opacity-70 dark:border-blue-800"
|
||||
/>
|
||||
<ActionIconButton
|
||||
type="submit"
|
||||
@@ -4775,7 +4775,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
}}
|
||||
tone="neutral"
|
||||
size="lg"
|
||||
class="order-2 sm:order-none"
|
||||
class="order-2 sm:order-0"
|
||||
title={AI_CHAT_CLOSE_LABEL}
|
||||
label={AI_CHAT_CLOSE_LABEL}
|
||||
data-testid="assistant-close-button"
|
||||
@@ -4787,7 +4787,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={transcriptCopyFallback()}>
|
||||
{(fallback) => (
|
||||
<section
|
||||
class="border-b border-amber-200 bg-amber-50 px-4 py-3 text-amber-950 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-100"
|
||||
class="border-b border-amber-200 bg-amber-50 px-4 py-3 text-amber-950/25 dark:border-amber-900/25 dark:bg-amber-950/30 dark:text-amber-100"
|
||||
aria-label={AI_CHAT_TRANSCRIPT_FALLBACK_TITLE}
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
@@ -4815,7 +4815,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
</div>
|
||||
<textarea
|
||||
ref={transcriptFallbackTextareaRef}
|
||||
class="h-36 w-full resize-y rounded-md border border-amber-200 bg-surface px-2 py-2 font-mono text-[11px] leading-relaxed text-base-content outline-none focus:border-amber-400 dark:border-amber-800 dark:bg-surface"
|
||||
class="h-36 w-full resize-y rounded-md border border-amber-200 bg-surface px-2 py-2 font-mono text-[11px] leading-relaxed text-base-content outline-hidden focus:border-amber-400 dark:border-amber-800 dark:bg-surface"
|
||||
readonly
|
||||
value={fallback().transcript}
|
||||
aria-label={AI_CHAT_TRANSCRIPT_FALLBACK_TEXTAREA_LABEL}
|
||||
@@ -4829,17 +4829,17 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<section
|
||||
class={`border-b px-4 py-2.5 text-[11px] ${
|
||||
presentation().tone === 'checking'
|
||||
? 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200'
|
||||
? 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950/25 dark:text-blue-200'
|
||||
: presentation().tone === 'ready'
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-100'
|
||||
: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-100'
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950/25 dark:text-emerald-100'
|
||||
: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/25 dark:text-amber-100'
|
||||
}`}
|
||||
aria-label="Assistant selected model route status"
|
||||
>
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="flex min-w-0 items-start gap-2.5">
|
||||
<span
|
||||
class={`mt-1 h-2 w-2 flex-shrink-0 rounded-full ${
|
||||
class={`mt-1 h-2 w-2 shrink-0 rounded-full ${
|
||||
presentation().tone === 'checking'
|
||||
? 'bg-blue-500 dark:bg-blue-300'
|
||||
: presentation().tone === 'ready'
|
||||
@@ -4879,7 +4879,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={switchToProviderReadinessAlternative}
|
||||
class="inline-flex max-w-[11rem] items-center gap-1.5 rounded-md border border-current/20 bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
class="inline-flex max-w-44 items-center gap-1.5 rounded-md border bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
aria-label={providerReadinessAlternativeButtonLabel()}
|
||||
title={alternative().label}
|
||||
>
|
||||
@@ -4893,7 +4893,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
type="button"
|
||||
onClick={retrySelectedProviderReadiness}
|
||||
disabled={providerReadiness().status === 'checking'}
|
||||
class="inline-flex items-center gap-1.5 rounded-md border border-current/20 bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-60"
|
||||
class="inline-flex items-center gap-1.5 rounded-md border bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-60"
|
||||
aria-label="Retry route check"
|
||||
>
|
||||
<RefreshCwIcon class="h-3.5 w-3.5" />
|
||||
@@ -4901,7 +4901,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
</button>
|
||||
<a
|
||||
href={AI_CHAT_PROVIDER_READINESS_SETTINGS_HREF}
|
||||
class="inline-flex items-center gap-1.5 rounded-md border border-current/20 bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
class="inline-flex items-center gap-1.5 rounded-md border bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
>
|
||||
<SettingsIcon class="h-3.5 w-3.5" />
|
||||
<span>{AI_CHAT_PROVIDER_READINESS_SETTINGS_LABEL}</span>
|
||||
@@ -4910,7 +4910,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setProviderReadinessVisible(false)}
|
||||
class="inline-flex items-center gap-1.5 rounded-md border border-current/20 bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
class="inline-flex items-center gap-1.5 rounded-md border bg-surface px-2 py-1 text-[10px] font-medium text-base-content hover:bg-surface-hover"
|
||||
aria-label="Hide route status"
|
||||
>
|
||||
<XIcon class="h-3.5 w-3.5" />
|
||||
@@ -4926,10 +4926,10 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
|
||||
{/* Discovery hint - show when discovery is disabled */}
|
||||
<Show when={discoveryEnabled() === false && !discoveryHintDismissed()}>
|
||||
<div class="px-4 py-2 border-b border-cyan-200 dark:border-cyan-800 bg-cyan-50 dark:bg-cyan-900 flex items-center justify-between gap-3 text-[11px] text-cyan-700 dark:text-cyan-200">
|
||||
<div class="px-4 py-2 border-b border-cyan-200 dark:border-cyan-800 bg-cyan-50 dark:bg-cyan-900/25 flex items-center justify-between gap-3 text-[11px] text-cyan-700 dark:text-cyan-200">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-cyan-500 dark:text-cyan-400 flex-shrink-0"
|
||||
class="w-4 h-4 text-cyan-500 dark:text-cyan-400 shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -4984,7 +4984,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={contextBriefing()!.actionHref && contextBriefing()!.actionLabel}>
|
||||
<a
|
||||
href={contextBriefing()!.actionHref}
|
||||
class="mt-2 inline-flex rounded border border-border bg-surface px-2 py-1 text-[11px] font-medium text-base-content hover:bg-surface-alt"
|
||||
class="mt-2 inline-flex rounded-sm border border-border bg-surface px-2 py-1 text-[11px] font-medium text-base-content hover:bg-surface-alt"
|
||||
>
|
||||
{contextBriefing()!.actionLabel}
|
||||
</a>
|
||||
@@ -5038,7 +5038,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
}
|
||||
>
|
||||
<div
|
||||
class="mb-2 overflow-hidden rounded-md border border-border bg-surface-alt text-base-content shadow-sm"
|
||||
class="mb-2 overflow-hidden rounded-md border border-border bg-surface-alt text-base-content shadow-xs"
|
||||
data-testid="assistant-activity-dock"
|
||||
>
|
||||
<Show when={currentStatus()}>
|
||||
@@ -5114,7 +5114,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={autonomousWarningVisible()}>
|
||||
<div
|
||||
class={`flex min-h-8 min-w-0 items-center gap-2 px-2.5 py-1.5 text-xs text-red-700 dark:text-red-200 ${
|
||||
currentStatus() ? 'border-t border-border/70' : ''
|
||||
currentStatus() ? 'border-t ' : ''
|
||||
}`}
|
||||
role="status"
|
||||
aria-label="Assistant chat actions warning"
|
||||
@@ -5130,7 +5130,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateControlLevel('controlled')}
|
||||
class="inline-flex shrink-0 items-center rounded-md border border-red-200 bg-surface px-2 py-1 text-[10px] font-medium text-red-700 transition-colors hover:bg-red-50 hover:text-red-900 dark:border-red-800 dark:bg-surface dark:text-red-200 dark:hover:bg-red-950/40"
|
||||
class="inline-flex shrink-0 items-center rounded-md border border-red-200 bg-surface px-2 py-1 text-[10px] font-medium text-red-700 transition-colors hover:bg-red-50 hover:text-red-900/25 dark:border-red-800 dark:bg-surface dark:text-red-200 dark:hover:bg-red-950/40"
|
||||
aria-label={AI_CHAT_SWITCH_TO_APPROVAL_LABEL}
|
||||
>
|
||||
Switch to Ask first
|
||||
@@ -5149,9 +5149,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={activityDockQueuedFollowUpCount() > 0}>
|
||||
<div
|
||||
class={`px-2.5 py-1.5 ${
|
||||
currentStatus() || autonomousWarningVisible()
|
||||
? 'border-t border-border/70'
|
||||
: ''
|
||||
currentStatus() || autonomousWarningVisible() ? 'border-t ' : ''
|
||||
}`}
|
||||
role="status"
|
||||
aria-label="Queued follow-up messages"
|
||||
@@ -5194,7 +5192,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
};
|
||||
return (
|
||||
<div
|
||||
class="flex min-h-7 items-center gap-2 rounded-md bg-white/70 px-2 py-1 text-xs text-blue-900 outline-none transition-colors focus:bg-white focus:ring-2 focus:ring-blue-500/40 dark:bg-blue-900/30 dark:text-blue-100 dark:focus:bg-blue-900/50"
|
||||
class="flex min-h-7 items-center gap-2 rounded-md bg-white/70 px-2 py-1 text-xs text-blue-900/25 outline-hidden transition-colors focus:bg-white focus:ring-2 focus:ring-blue-500/40 dark:bg-blue-900/30 dark:text-blue-100 dark:focus:bg-blue-900/50"
|
||||
classList={{
|
||||
'bg-blue-50 ring-2 ring-blue-500/60 dark:bg-blue-800/50':
|
||||
queuedFollowUpCommandTargetId() === queued.id,
|
||||
@@ -5372,7 +5370,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
class="relative"
|
||||
>
|
||||
<div
|
||||
class={`relative flex min-h-[56px] items-end rounded-lg border bg-surface-alt shadow-sm transition-colors ${
|
||||
class={`relative flex min-h-[56px] items-end rounded-lg border bg-surface-alt shadow-xs transition-colors ${
|
||||
mentionActive() || slashCommandActive()
|
||||
? 'border-blue-400 ring-2 ring-blue-500/20'
|
||||
: 'border-border focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/20'
|
||||
@@ -5402,7 +5400,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
: undefined
|
||||
}
|
||||
rows={1}
|
||||
class="max-h-40 min-h-[54px] flex-1 resize-none bg-transparent px-3.5 py-3.5 pr-14 text-sm leading-5 text-base-content placeholder-slate-400 focus:outline-none"
|
||||
class="max-h-40 min-h-[54px] flex-1 resize-none bg-transparent px-3.5 py-3.5 pr-14 text-sm leading-5 text-base-content placeholder-slate-400 focus:outline-hidden"
|
||||
/>
|
||||
<div data-mention-autocomplete>
|
||||
<MentionAutocomplete
|
||||
@@ -5469,7 +5467,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
aria-label="Assistant selected model route health"
|
||||
aria-live="polite"
|
||||
title={health().title}
|
||||
class={`inline-flex h-7 max-w-[11rem] shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
class={`inline-flex h-7 max-w-44 shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
data-testid="assistant-provider-route-health"
|
||||
>
|
||||
<span
|
||||
@@ -5487,7 +5485,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
aria-label="Assistant capability availability"
|
||||
aria-live="polite"
|
||||
title={health().title}
|
||||
class={`inline-flex h-7 max-w-[9rem] shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
class={`inline-flex h-7 max-w-36 shrink-0 items-center gap-1.5 rounded-md border px-2 text-[10px] font-medium ${health().className}`}
|
||||
data-testid="assistant-surface-tools-health"
|
||||
>
|
||||
<WrenchIcon
|
||||
@@ -5537,7 +5535,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
ref={controlModeButtonRef}
|
||||
onClick={toggleControlMenu}
|
||||
onKeyDown={handleControlModeTriggerKeyDown}
|
||||
class={`flex flex-shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium rounded-md border transition-colors ${controlPresentation().pillClassName} ${controlSaving() ? 'opacity-70 cursor-wait' : 'hover:opacity-90'}`}
|
||||
class={`flex shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium rounded-md border transition-colors ${controlPresentation().pillClassName} ${controlSaving() ? 'opacity-70 cursor-wait' : 'hover:opacity-90'}`}
|
||||
title={AI_CHAT_CONTROL_MODE_LABEL}
|
||||
aria-label={`${AI_CHAT_CONTROL_MODE_LABEL}: ${controlPresentation().label}`}
|
||||
aria-haspopup="menu"
|
||||
@@ -5560,7 +5558,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
|
||||
<Show when={showControlMenu()}>
|
||||
<div
|
||||
class="absolute bottom-full left-0 z-50 mb-2 w-60 overflow-hidden rounded-md border border-border bg-surface shadow-sm"
|
||||
class="absolute bottom-full left-0 z-50 mb-2 w-60 overflow-hidden rounded-md border border-border bg-surface shadow-xs"
|
||||
role="menu"
|
||||
aria-label={AI_CHAT_CONTROL_MODE_MENU_LABEL}
|
||||
>
|
||||
@@ -5629,7 +5627,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
<Show when={lastAssistantTurnSummary()}>
|
||||
{(summary) => (
|
||||
<div
|
||||
class="flex h-5 min-w-0 items-center justify-end self-end text-[10px] font-medium text-muted sm:h-7 sm:max-w-[14rem] sm:shrink-0 sm:self-auto"
|
||||
class="flex h-5 min-w-0 items-center justify-end self-end text-[10px] font-medium text-muted sm:h-7 sm:max-w-56 sm:shrink-0 sm:self-auto"
|
||||
aria-label={summary().title}
|
||||
title={summary().title}
|
||||
>
|
||||
|
||||
@@ -1037,7 +1037,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
class={`p-3 transition-colors ${
|
||||
isPatrolFindingsSource()
|
||||
? `border-l-2 ${patrolSeverityAccentClass(finding.severity)} ${
|
||||
expandedId() === finding.id ? 'bg-surface-alt/80' : 'hover:bg-surface-hover'
|
||||
expandedId() === finding.id ? '' : 'hover:bg-surface-hover'
|
||||
}`
|
||||
: ''
|
||||
} ${
|
||||
@@ -1065,7 +1065,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
toggleExpanded();
|
||||
}
|
||||
}}
|
||||
class="min-w-0 flex-1 cursor-pointer rounded text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
class="min-w-0 flex-1 cursor-pointer rounded-sm text-left focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
{/* Status badge for non-active findings */}
|
||||
@@ -1318,7 +1318,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleExpanded}
|
||||
class="rounded bg-amber-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-amber-700"
|
||||
class="rounded-sm bg-amber-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-amber-700"
|
||||
title={action().detail}
|
||||
>
|
||||
{action().label}
|
||||
@@ -1330,7 +1330,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<a
|
||||
href={action().href}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
class="inline-flex items-center rounded bg-blue-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
class="inline-flex items-center rounded-sm bg-blue-600 px-2 py-1 text-xs font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
>
|
||||
{action().label}
|
||||
</a>
|
||||
@@ -1406,7 +1406,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
aria-expanded={manageOpenId() === finding.id}
|
||||
aria-controls={`finding-${finding.id}-manage`}
|
||||
onClick={toggleFindingOptions}
|
||||
class="inline-flex min-h-11 items-center rounded border border-border bg-surface px-2 py-1 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
class="inline-flex min-h-11 items-center rounded-sm border border-border bg-surface px-2 py-1 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
title="Resolve, dismiss, remember as expected, or create a rule"
|
||||
>
|
||||
Finding options
|
||||
@@ -1422,7 +1422,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
aria-expanded={expandedId() === finding.id}
|
||||
aria-controls={`finding-${finding.id}-details`}
|
||||
onClick={toggleExpanded}
|
||||
class="inline-flex min-h-11 items-center gap-1 rounded border border-border bg-surface px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
class="inline-flex min-h-11 items-center gap-1 rounded-sm border border-border bg-surface px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40 sm:min-h-0"
|
||||
title={
|
||||
isPatrolFindingsSource()
|
||||
? expandedId() === finding.id
|
||||
@@ -1536,7 +1536,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={finding.previousResolvedFixSummary}>
|
||||
<p class="text-sm text-base-content mt-2 px-2 py-1 rounded border border-emerald-200 bg-emerald-50/40 dark:border-emerald-800 dark:bg-emerald-950/30">
|
||||
<p class="text-sm text-base-content mt-2 px-2 py-1 rounded-sm border border-emerald-200 bg-emerald-50/40 dark:border-emerald-800 dark:bg-emerald-950/30">
|
||||
<span class="font-medium text-emerald-800 dark:text-emerald-300">
|
||||
Last time this resolved:
|
||||
</span>{' '}
|
||||
@@ -1554,7 +1554,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<Show when={relatedFindings.length > 0}>
|
||||
<div class="mt-3 rounded border border-border bg-surface-alt p-2">
|
||||
<div class="mt-3 rounded-sm border border-border bg-surface-alt p-2">
|
||||
<div class="text-xs font-medium text-base-content">Related issues</div>
|
||||
<div class="mt-2 space-y-1">
|
||||
<For each={relatedFindings}>
|
||||
@@ -1584,7 +1584,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<Show when={shouldShowFindingLifecycle(finding)}>
|
||||
<div class="mt-3 p-2 rounded border border-border bg-surface-alt">
|
||||
<div class="mt-3 p-2 rounded-sm border border-border bg-surface-alt">
|
||||
<div class="text-xs font-medium text-base-content mb-2">Lifecycle</div>
|
||||
<div class="space-y-1">
|
||||
<For each={[...(finding.lifecycle || [])].slice(-6).reverse()}>
|
||||
@@ -1630,11 +1630,11 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
{/* User note display / editor */}
|
||||
<Show when={editingNoteId() === finding.id}>
|
||||
<div
|
||||
class="mt-3 p-2 rounded border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900"
|
||||
class="mt-3 p-2 rounded-sm border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/25"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<textarea
|
||||
class="w-full text-sm rounded border border-border bg-surface text-base-content px-2 py-1.5 resize-none focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full text-sm rounded-sm border border-border bg-surface text-base-content px-2 py-1.5 resize-none focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
rows={3}
|
||||
value={noteText()}
|
||||
onInput={(e) => setNoteText(e.currentTarget.value)}
|
||||
@@ -1644,7 +1644,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSaveNote(finding, e)}
|
||||
class="px-3 py-1 text-xs font-medium rounded bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Save
|
||||
@@ -1652,7 +1652,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancelNote}
|
||||
class="px-3 py-1 text-xs font-medium rounded border border-border hover:bg-surface-hover"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm border border-border hover:bg-surface-hover"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1660,9 +1660,9 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={editingNoteId() !== finding.id && finding.userNote}>
|
||||
<div class="mt-3 p-2 rounded border border-border bg-surface-alt flex items-start gap-2">
|
||||
<div class="mt-3 p-2 rounded-sm border border-border bg-surface-alt flex items-start gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-muted mt-0.5 flex-shrink-0"
|
||||
class="w-4 h-4 text-muted mt-0.5 shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -1678,7 +1678,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartEditNote(finding, e)}
|
||||
class="p-1 hover:text-base-content flex-shrink-0"
|
||||
class="p-1 hover:text-base-content shrink-0"
|
||||
title="Edit note"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -1702,7 +1702,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<a
|
||||
href={action().href}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
>
|
||||
{action().label}
|
||||
</a>
|
||||
@@ -1715,7 +1715,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
e.stopPropagation();
|
||||
void openFindingInAssistant(finding);
|
||||
}}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
title={getPrimaryAssistantFindingAction(finding).title}
|
||||
>
|
||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -1745,7 +1745,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
type="button"
|
||||
onClick={(e) => handleInvestigate(finding, e)}
|
||||
disabled={actionLoading() === finding.id}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
title="Have Patrol investigate this finding"
|
||||
>
|
||||
{actionLoading() === finding.id ? 'Investigating…' : 'Investigate'}
|
||||
@@ -1773,7 +1773,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
props.onPatrolModeNudgeAction?.();
|
||||
}}
|
||||
disabled={props.patrolModeNudgeBusy}
|
||||
class="inline-flex items-center gap-1 rounded bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-blue-600 px-3 py-1.5 font-semibold text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
title="Switch Patrol to Ask first so it can investigate and prepare fixes for your approval"
|
||||
>
|
||||
{props.patrolModeNudgeBusy ? 'Switching…' : modeNudge!.actionLabel}
|
||||
@@ -1788,10 +1788,10 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onToggle={(event) => setManageOpenId(event.currentTarget.open ? finding.id : null)}
|
||||
>
|
||||
<summary class="list-none cursor-pointer rounded border border-border bg-surface px-3 py-1.5 font-medium text-base-content hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40">
|
||||
<summary class="list-none cursor-pointer rounded-sm border border-border bg-surface px-3 py-1.5 font-medium text-base-content hover:bg-surface-hover focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40">
|
||||
Resolve or dismiss
|
||||
</summary>
|
||||
<div class="mt-1 flex min-w-48 flex-col gap-1 rounded border border-border bg-surface p-1 shadow-sm">
|
||||
<div class="mt-1 flex min-w-48 flex-col gap-1 rounded-sm border border-border bg-surface p-1 shadow-xs">
|
||||
<Show when={shouldShowAssistantManageAction}>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1799,7 +1799,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
e.stopPropagation();
|
||||
void openFindingInAssistant(finding);
|
||||
}}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover"
|
||||
title={getPrimaryAssistantFindingAction(finding).title}
|
||||
>
|
||||
{getPrimaryAssistantFindingAction(finding).label}
|
||||
@@ -1809,7 +1809,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartEditNote(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover"
|
||||
>
|
||||
Add Note
|
||||
</button>
|
||||
@@ -1817,7 +1817,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleCopyFindingSummary(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover"
|
||||
>
|
||||
Copy summary
|
||||
</button>
|
||||
@@ -1826,7 +1826,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleAcknowledge(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Acknowledge
|
||||
@@ -1836,7 +1836,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleResolve(finding, e)}
|
||||
class="rounded px-2 py-1 text-left text-emerald-700 hover:bg-emerald-50 disabled:opacity-50 dark:text-emerald-300 dark:hover:bg-emerald-900"
|
||||
class="rounded-sm px-2 py-1 text-left text-emerald-700 hover:bg-emerald-50 disabled:opacity-50 dark:text-emerald-300 dark:hover:bg-emerald-900/25"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Mark resolved
|
||||
@@ -1846,7 +1846,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSnooze(finding, 1, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Snooze 1h
|
||||
@@ -1854,7 +1854,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSnooze(finding, 24, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Snooze 24h
|
||||
@@ -1862,7 +1862,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleSnooze(finding, 168, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Snooze 7d
|
||||
@@ -1872,7 +1872,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartDismiss(finding, 'not_an_issue', e)}
|
||||
class="rounded px-2 py-1 text-left text-red-600 hover:bg-red-50 disabled:opacity-50 dark:text-red-400 dark:hover:bg-red-900"
|
||||
class="rounded-sm px-2 py-1 text-left text-red-600 hover:bg-red-50 disabled:opacity-50 dark:text-red-400 dark:hover:bg-red-900/25"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Dismiss: Not an issue
|
||||
@@ -1880,7 +1880,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartDismiss(finding, 'expected_behavior', e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Remember as expected
|
||||
@@ -1888,7 +1888,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartDismiss(finding, 'will_fix_later', e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Dismiss: Later
|
||||
@@ -1896,7 +1896,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleStartCreateRule(finding, e)}
|
||||
class="rounded px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
class="rounded-sm px-2 py-1 text-left hover:bg-surface-hover disabled:opacity-50"
|
||||
disabled={
|
||||
actionLoading() === finding.id ||
|
||||
!getFindingSuppressionRuleScope(finding).canCreate
|
||||
@@ -1915,7 +1915,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleReopen(finding, e)}
|
||||
class="rounded px-2 py-1 text-left text-blue-700 hover:bg-blue-50 disabled:opacity-50 dark:text-blue-300 dark:hover:bg-blue-900"
|
||||
class="rounded-sm px-2 py-1 text-left text-blue-700 hover:bg-blue-50 disabled:opacity-50 dark:text-blue-300 dark:hover:bg-blue-900/25"
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Reopen finding
|
||||
@@ -1931,7 +1931,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
visually but uses neutral surface styling — this isn't a
|
||||
dismissal, it's a permanent commitment. */}
|
||||
<Show when={creatingRuleForId() === finding.id}>
|
||||
<div class="mt-2 p-2 rounded border border-border bg-surface-alt">
|
||||
<div class="mt-2 p-2 rounded-sm border border-border bg-surface-alt">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="text-xs font-medium text-base-content">
|
||||
Create suppression rule for{' '}
|
||||
@@ -1947,7 +1947,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
suppressions management surface.
|
||||
</p>
|
||||
<textarea
|
||||
class="w-full text-xs px-2 py-1.5 rounded border border-border bg-surface text-base-content resize-none focus:outline-none focus:ring-1 focus:ring-blue-400"
|
||||
class="w-full text-xs px-2 py-1.5 rounded-sm border border-border bg-surface text-base-content resize-none focus:outline-hidden focus:ring-1 focus:ring-blue-400"
|
||||
rows={2}
|
||||
value={createRuleDescription()}
|
||||
onInput={(e) => setCreateRuleDescription(e.currentTarget.value)}
|
||||
@@ -1958,7 +1958,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleConfirmCreateRule(finding, e)}
|
||||
class="px-3 py-1 text-xs font-medium rounded bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50"
|
||||
disabled={actionLoading() === finding.id || !createRuleDescription().trim()}
|
||||
>
|
||||
Create rule
|
||||
@@ -1966,7 +1966,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancelCreateRule}
|
||||
class="px-3 py-1 text-xs font-medium rounded border border-border hover:bg-surface-hover"
|
||||
class="px-3 py-1 text-xs font-medium rounded-sm border border-border hover:bg-surface-hover"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1978,7 +1978,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
"Pulse should know this is expected"), "Dismiss as" for the
|
||||
other reasons (past-looking, "make this go away"). */}
|
||||
<Show when={dismissingId() === finding.id}>
|
||||
<div class="mt-2 p-2 rounded border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900">
|
||||
<div class="mt-2 p-2 rounded-sm border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="text-xs font-medium text-red-700 dark:text-red-300">
|
||||
{dismissReason() === 'expected_behavior'
|
||||
@@ -2024,7 +2024,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</p>
|
||||
</Show>
|
||||
<textarea
|
||||
class="w-full text-xs px-2 py-1.5 rounded border border-border bg-surface text-base-content resize-none focus:outline-none focus:ring-1 focus:ring-red-400"
|
||||
class="w-full text-xs px-2 py-1.5 rounded-sm border border-border bg-surface text-base-content resize-none focus:outline-hidden focus:ring-1 focus:ring-red-400"
|
||||
rows={2}
|
||||
placeholder="Optional note (for learning context)..."
|
||||
value={dismissNote()}
|
||||
@@ -2036,14 +2036,14 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
type="button"
|
||||
onClick={(e) => handleConfirmDismiss(finding.id, e)}
|
||||
disabled={actionLoading() === finding.id}
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-red-600 hover:bg-red-700 disabled:bg-red-400 rounded transition-colors"
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-red-600 hover:bg-red-700 disabled:bg-red-400 rounded-sm transition-colors"
|
||||
>
|
||||
Confirm Dismiss
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancelDismiss}
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors"
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded-sm transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -2099,21 +2099,21 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<div class="mt-3 pt-3 border-t border-border-subtle">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<AlertCircleIcon class="h-4 w-4 flex-shrink-0 text-muted" />
|
||||
<AlertCircleIcon class="h-4 w-4 shrink-0 text-muted" />
|
||||
<span class="text-sm font-medium text-base-content">Assistant context</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleOpenPlanInAssistant(finding, plan(), e)}
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium rounded flex items-center justify-center gap-1.5"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium rounded-sm flex items-center justify-center gap-1.5"
|
||||
>
|
||||
Ask Assistant
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleDismissPlan(plan(), e)}
|
||||
class="px-3 py-1.5 hover:bg-surface-hover text-muted text-xs font-medium rounded"
|
||||
class="px-3 py-1.5 hover:bg-surface-hover text-muted text-xs font-medium rounded-sm"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
@@ -2145,7 +2145,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
fieldBaseClass="contents"
|
||||
value={sortBy()}
|
||||
onChange={(e) => setSortBy(e.currentTarget.value as 'severity' | 'time')}
|
||||
selectBaseClass="min-h-11 rounded border border-border bg-surface px-2 py-1 text-xs sm:min-h-0"
|
||||
selectBaseClass="min-h-11 rounded-sm border border-border bg-surface px-2 py-1 text-xs sm:min-h-0"
|
||||
>
|
||||
<option value="severity">By Severity</option>
|
||||
<option value="time">By Time</option>
|
||||
@@ -2243,7 +2243,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
class="divide-y divide-border-subtle"
|
||||
aria-label={`${group.label}: ${getPatrolFindingIssueCountLabel(group.findings.length)}`}
|
||||
>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 bg-surface-alt/60 px-3 py-2 text-xs">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 px-3 py-2 text-xs">
|
||||
<span class="font-semibold text-base-content">{group.label}</span>
|
||||
<span class="text-muted">
|
||||
{getPatrolFindingIssueCountLabel(group.findings.length)}
|
||||
@@ -2261,8 +2261,8 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
)}
|
||||
</For>
|
||||
<Show when={alertMirroredFindings().length > 0}>
|
||||
<details class="bg-surface-alt/40">
|
||||
<summary class="flex min-h-11 cursor-pointer flex-wrap items-center justify-between gap-2 px-3 py-2 text-xs focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500">
|
||||
<details class="">
|
||||
<summary class="flex min-h-11 cursor-pointer flex-wrap items-center justify-between gap-2 px-3 py-2 text-xs focus:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500">
|
||||
<span class="font-semibold text-base-content">
|
||||
{alertMirroredFindings().length}{' '}
|
||||
{alertMirroredFindings().length === 1 ? 'finding mirrors' : 'findings mirror'}{' '}
|
||||
@@ -2293,9 +2293,9 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
<aside
|
||||
id={`finding-${finding().id}-details`}
|
||||
aria-label={`Review ${title().label}`}
|
||||
class="overflow-hidden rounded-md border border-border bg-surface shadow-sm xl:sticky xl:top-4"
|
||||
class="overflow-hidden rounded-md border border-border bg-surface shadow-xs xl:sticky xl:top-4"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3 border-b border-border-subtle bg-surface-alt/60 px-4 py-3">
|
||||
<div class="flex items-start justify-between gap-3 border-b border-border-subtle px-4 py-3">
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<MetadataBadge
|
||||
@@ -2319,7 +2319,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
type="button"
|
||||
aria-label={`Close review panel for ${title().label}`}
|
||||
onClick={closeReviewPanel}
|
||||
class="rounded p-1.5 text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
class="rounded-sm p-1.5 text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/40"
|
||||
>
|
||||
<XIcon class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@@ -178,7 +178,9 @@ describe('FindingsPanel assistant handoff', () => {
|
||||
// row body itself opens the same details panel for normal scanning.
|
||||
expect(findingsPanelSource).toContain('role="button"');
|
||||
expect(findingsPanelSource).toContain('tabIndex={0}');
|
||||
expect(findingsPanelSource).toContain('class="min-w-0 flex-1 cursor-pointer rounded text-left');
|
||||
expect(findingsPanelSource).toContain(
|
||||
'class="min-w-0 flex-1 cursor-pointer rounded-sm text-left',
|
||||
);
|
||||
expect(findingsPanelSource).toContain('aria-expanded={expandedId() === finding.id}');
|
||||
expect(findingsPanelSource).toContain(
|
||||
"aria-label={`${expandedId() === finding.id ? 'Close issue details' : 'Open issue details'} for ${title.label}`}",
|
||||
@@ -563,7 +565,7 @@ describe('aiFindingPresentation', () => {
|
||||
).toEqual({
|
||||
label: 'Runtime issue',
|
||||
badgeClasses:
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900 dark:text-sky-300',
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900/25 dark:text-sky-300',
|
||||
badgeTone: 'sky',
|
||||
uppercase: false,
|
||||
});
|
||||
@@ -671,7 +673,7 @@ describe('aiFindingPresentation', () => {
|
||||
kind: 'runtime',
|
||||
label: 'Patrol runtime',
|
||||
badgeClasses:
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900 dark:text-sky-300',
|
||||
'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-900/25 dark:text-sky-300',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -944,10 +946,10 @@ describe('aiFindingPresentation', () => {
|
||||
|
||||
it('keeps Patrol review and sort controls touch-sized on phones', () => {
|
||||
expect(findingsPanelSource).toContain(
|
||||
'class="inline-flex min-h-11 items-center gap-1 rounded border border-border',
|
||||
'class="inline-flex min-h-11 items-center gap-1 rounded-sm border border-border',
|
||||
);
|
||||
expect(findingsPanelSource).toContain(
|
||||
'selectBaseClass="min-h-11 rounded border border-border',
|
||||
'selectBaseClass="min-h-11 rounded-sm border border-border',
|
||||
);
|
||||
expect(findingsPanelSource).toContain('sm:min-h-0');
|
||||
});
|
||||
@@ -1093,7 +1095,7 @@ describe('aiFindingPresentation', () => {
|
||||
expect(findingsPanelSource).toContain('severityPresentation.badgeTone');
|
||||
expect(findingsPanelSource).toContain('severityPresentation.label');
|
||||
expect(findingsPanelSource).not.toContain(
|
||||
'px-1.5 py-0.5 border text-[10px] font-medium rounded',
|
||||
'px-1.5 py-0.5 border text-[10px] font-medium rounded-sm',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ export const renderMarkdown = (content: unknown): string => {
|
||||
for (const table of template.content.querySelectorAll('table')) {
|
||||
const region = document.createElement('div');
|
||||
region.className =
|
||||
'max-w-full overflow-x-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500';
|
||||
'max-w-full overflow-x-auto focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-blue-500';
|
||||
region.tabIndex = 0;
|
||||
region.setAttribute('role', 'region');
|
||||
region.setAttribute('aria-label', 'Scrollable table');
|
||||
|
||||
@@ -96,7 +96,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded-sm transition-colors duration-150 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
@@ -195,7 +195,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
});
|
||||
}}
|
||||
onChange={(e) => props.toggleAll(e.currentTarget.checked)}
|
||||
class="rounded text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
class="rounded-sm text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
aria-label="Select all resources"
|
||||
/>
|
||||
</TableHead>
|
||||
@@ -209,7 +209,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
<For each={props.table.columns}>
|
||||
{(column) => (
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind(getAlertResourceColumnKind(column))} whitespace-normal break-words`}
|
||||
class={`${getPlatformTableHeadClassForKind(getAlertResourceColumnKind(column))} whitespace-normal wrap-break-word`}
|
||||
title={getAlertResourceColumnHeaderTooltip(column)}
|
||||
>
|
||||
{column}
|
||||
@@ -260,7 +260,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
props.table.onToggleGlobalDisable?.();
|
||||
props.table.setHasUnsavedChanges?.(true);
|
||||
}}
|
||||
class="my-[1px]"
|
||||
class="my-px"
|
||||
title="Global alerts toggle - disable all alerts for this resource type"
|
||||
ariaLabel="Global alerts toggle"
|
||||
/>
|
||||
@@ -271,7 +271,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-semibold text-base-content">Global Defaults</span>
|
||||
<Show when={props.hasCustomGlobalDefaults()}>
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{getAlertResourceTableCustomBadgeLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -328,7 +328,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
<Show when={isOff()}>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute inset-0 w-full rounded cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
class="absolute inset-0 w-full rounded-sm cursor-pointer focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
onClick={() => {
|
||||
props.table.setGlobalDefaults?.((prev) => ({
|
||||
...prev,
|
||||
@@ -501,7 +501,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
min="0"
|
||||
value={overrideDelay !== undefined ? overrideDelay : ''}
|
||||
placeholder={String(typeDefaultDelay)}
|
||||
class="w-16 rounded border border-border bg-surface px-2 py-0.5 text-sm text-center text-base-content focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
||||
class="w-16 rounded-sm border border-border bg-surface px-2 py-0.5 text-sm text-center text-base-content focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
||||
onInput={(e) => {
|
||||
const raw = e.currentTarget.value;
|
||||
if (raw === '') {
|
||||
|
||||
@@ -121,7 +121,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={`inline-flex min-h-11 items-center justify-center px-2 py-0.5 text-xs font-medium rounded transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 sm:min-h-0 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
class={`inline-flex min-h-11 items-center justify-center px-2 py-0.5 text-xs font-medium rounded-sm transition-colors duration-150 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 sm:min-h-0 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
@@ -179,13 +179,13 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
>
|
||||
<Card
|
||||
padding="sm"
|
||||
class="border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900"
|
||||
class="border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/25"
|
||||
>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold text-sm">Global Defaults</span>
|
||||
<Show when={props.hasCustomGlobalDefaults()}>
|
||||
<span class="text-[10px] px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="text-[10px] px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{getAlertResourceTableCustomBadgeLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -218,7 +218,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const isOff = () => isAlertResourceMetricOff(value());
|
||||
|
||||
return (
|
||||
<div class="p-2 bg-surface rounded border border-border-subtle flex flex-col gap-1">
|
||||
<div class="p-2 bg-surface rounded-sm border border-border-subtle flex flex-col gap-1">
|
||||
<span class="text-[10px] uppercase text-slate-500 font-medium">{column}</span>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="relative flex-1">
|
||||
@@ -230,7 +230,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
value={isOff() ? '' : value()}
|
||||
placeholder={getAlertResourceTableMetricPlaceholder(isOff())}
|
||||
disabled={isOff()}
|
||||
class={`min-h-11 w-full rounded border p-1 text-center text-sm sm:min-h-0 ${isOff() ? 'bg-surface-hover' : ' border-border'}`}
|
||||
class={`min-h-11 w-full rounded-sm border p-1 text-center text-sm sm:min-h-0 ${isOff() ? 'bg-surface-hover' : ' border-border'}`}
|
||||
onInput={(e) => {
|
||||
const nextValue = parseFloat(e.currentTarget.value);
|
||||
// An empty box is mid-edit, not a disable request:
|
||||
@@ -284,7 +284,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
}}
|
||||
</For>
|
||||
<Show when={props.table.showOfflineAlertsColumn}>
|
||||
<div class="p-2 bg-surface rounded border border-border-subtle flex flex-col gap-1">
|
||||
<div class="p-2 bg-surface rounded-sm border border-border-subtle flex flex-col gap-1">
|
||||
<span class="text-[10px] uppercase text-slate-500 font-medium">Offline alerts</span>
|
||||
<div class="flex items-center min-h-11 sm:min-h-0">
|
||||
<Show
|
||||
@@ -473,7 +473,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
label="Override note"
|
||||
labelClass="sr-only"
|
||||
fieldBaseClass="w-full"
|
||||
textareaBaseClass="w-full text-xs p-2 rounded border border-border bg-surface-alt"
|
||||
textareaBaseClass="w-full text-xs p-2 rounded-sm border border-border bg-surface-alt"
|
||||
rows={2}
|
||||
placeholder={getAlertResourceTableEditNotePlaceholder()}
|
||||
value={props.table.editingNote()}
|
||||
@@ -505,7 +505,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const titlePrefix = metric === 'backup' ? 'Backup' : 'Snapshot';
|
||||
|
||||
return (
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded">
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded-sm">
|
||||
<span class="text-[10px] uppercase font-bold tracking-wider">
|
||||
{column}
|
||||
</span>
|
||||
@@ -524,7 +524,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const bounds = getAlertResourceMetricBounds(metric);
|
||||
|
||||
return (
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded">
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded-sm">
|
||||
<span class="text-[10px] uppercase font-bold tracking-wider">
|
||||
{column.replace(/mb\/s|%|°c/gi, '').trim()}
|
||||
</span>
|
||||
@@ -535,7 +535,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => startEditing(resource, metric, e)}
|
||||
class="min-h-11 min-w-11 font-mono text-xs font-medium cursor-pointer rounded px-1 -mx-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
class="min-h-11 min-w-11 font-mono text-xs font-medium cursor-pointer rounded-sm px-1 -mx-1 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400"
|
||||
aria-label={`Edit ${column} threshold for ${getAlertResourceLabel(resource)}`}
|
||||
>
|
||||
<MetricValueWithHeat
|
||||
@@ -554,7 +554,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
max={bounds.max}
|
||||
value={isDisabled() ? '' : (thresholds()?.[metric] ?? '')}
|
||||
placeholder={getAlertResourceTableMetricPlaceholder(isDisabled())}
|
||||
class="min-h-11 w-16 text-right text-xs p-1 rounded border border-border bg-surface"
|
||||
class="min-h-11 w-16 text-right text-xs p-1 rounded-sm border border-border bg-surface"
|
||||
onInput={(e) => {
|
||||
const nextValue = parseFloat(e.currentTarget.value);
|
||||
if (
|
||||
@@ -601,7 +601,7 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
const disabledGlobally = () => props.table.globalDisableFlag?.() ?? false;
|
||||
|
||||
return (
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded">
|
||||
<div class="flex justify-between items-center p-1.5 bg-surface-alt rounded-sm">
|
||||
<span class="text-[10px] uppercase font-bold tracking-wider">Offline</span>
|
||||
<Show
|
||||
when={supportsTriState}
|
||||
|
||||
@@ -203,7 +203,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
class={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded-sm transition-colors duration-150 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-1 ${config.className} ${disabled ? 'opacity-60 cursor-not-allowed pointer-events-none' : ''}`.trim()}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
@@ -228,7 +228,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
type="checkbox"
|
||||
checked={props.selected}
|
||||
onChange={(e) => props.onToggleSelection?.(e.currentTarget.checked)}
|
||||
class="rounded border-border text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
class="rounded-sm border-border text-sky-600 focus:ring-sky-500 transition-shadow cursor-pointer"
|
||||
aria-label={`Select ${resourceLabel()}`}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -246,7 +246,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
checked={isChecked}
|
||||
disabled={globallyDisabled}
|
||||
onToggle={() => !globallyDisabled && props.onToggleDisabled?.(props.resource.id)}
|
||||
class="my-[1px]"
|
||||
class="my-px"
|
||||
title={
|
||||
globallyDisabled
|
||||
? 'Alerts disabled globally'
|
||||
@@ -289,14 +289,14 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
title={`Open ${resourceLabel()} web interface`}
|
||||
/>
|
||||
<Show when={props.resource.clusterName}>
|
||||
<span class="rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||
<span class="rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900/25 dark:text-blue-300">
|
||||
{props.resource.clusterName}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={props.resource.type === 'storage' && props.resource.node}>
|
||||
<span class="rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300">
|
||||
<span class="rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300">
|
||||
{props.resource.node}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -304,7 +304,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
<span class="text-xs text-muted">{props.resource.subtitle as string}</span>
|
||||
</Show>
|
||||
<Show when={props.resource.hasOverride || props.resource.disableConnectivity}>
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{getAlertResourceTableCustomBadgeLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -315,7 +315,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
label="Override note"
|
||||
labelClass="sr-only"
|
||||
fieldBaseClass="mt-2 w-full"
|
||||
textareaBaseClass="w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
textareaBaseClass="w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
rows={2}
|
||||
placeholder={getAlertResourceTableOverrideNotePlaceholder()}
|
||||
value={props.editingNote()}
|
||||
@@ -323,7 +323,9 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!isEditing() && props.resource.note}>
|
||||
<p class="mt-2 text-xs italic text-muted break-words">{props.resource.note as string}</p>
|
||||
<p class="mt-2 text-xs italic text-muted wrap-break-word">
|
||||
{props.resource.note as string}
|
||||
</p>
|
||||
</Show>
|
||||
</TableCell>
|
||||
|
||||
@@ -394,7 +396,7 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
openMetricEditor();
|
||||
}
|
||||
}}
|
||||
class="cursor-pointer hover:bg-surface-hover rounded px-1 py-0.5 transition-colors"
|
||||
class="cursor-pointer hover:bg-surface-hover rounded-sm px-1 py-0.5 transition-colors"
|
||||
title={getAlertResourceTableEditMetricTitle()}
|
||||
aria-label={getAlertResourceTableEditMetricTitle()}
|
||||
>
|
||||
|
||||
@@ -96,7 +96,7 @@ export function BulkEditDialog(props: BulkEditDialogProps) {
|
||||
|
||||
return (
|
||||
<Dialog isOpen={props.isOpen} onClose={props.onClose} ariaLabel={getAlertBulkEditOpenLabel()}>
|
||||
<div class="fixed inset-0 min-h-screen z-[100] flex items-center justify-center pointer-events-none">
|
||||
<div class="fixed inset-0 min-h-screen z-100 flex items-center justify-center pointer-events-none">
|
||||
<div class="bg-surface rounded-xl shadow-2xl ring-1 ring-border max-w-lg w-full p-6 max-h-[90vh] flex flex-col pointer-events-auto">
|
||||
<h2 class="text-xl font-semibold text-base-content mb-2">
|
||||
{ALERT_BULK_EDIT_DIALOG_TITLE}
|
||||
@@ -163,7 +163,7 @@ export function BulkEditDialog(props: BulkEditDialogProps) {
|
||||
<input
|
||||
type="number"
|
||||
aria-label={`${column} threshold`}
|
||||
class="w-full h-9 rounded-md border border-border bg-surface px-3 py-1 text-sm shadow-sm transition-colors focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
|
||||
class="w-full h-9 rounded-md border border-border bg-surface px-3 py-1 text-sm shadow-xs transition-colors focus:border-sky-500 focus:outline-hidden focus:ring-1 focus:ring-sky-500"
|
||||
min={bounds.min}
|
||||
max={bounds.max}
|
||||
step={bounds.step}
|
||||
@@ -209,14 +209,14 @@ export function BulkEditDialog(props: BulkEditDialogProps) {
|
||||
<div class="mt-4 flex justify-end gap-3 pt-4 border-t border-border shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="px-5 py-2 text-sm font-medium text-base-content bg-surface border border-border hover:bg-surface-hover rounded-md transition-colors shadow-sm"
|
||||
class="px-5 py-2 text-sm font-medium text-base-content bg-surface border border-border hover:bg-surface-hover rounded-md transition-colors shadow-xs"
|
||||
onClick={props.onClose}
|
||||
>
|
||||
{ALERT_BULK_EDIT_CANCEL_LABEL}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-5 py-2 text-sm font-medium text-white bg-sky-600 hover:bg-sky-500 rounded-md shadow-sm transition-colors"
|
||||
class="px-5 py-2 text-sm font-medium text-white bg-sky-600 hover:bg-sky-500 rounded-md shadow-xs transition-colors"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{getAlertBulkEditApplyLabel(props.selectedIds.length)}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function EmailProviderSelect(props: EmailProviderSelectProps) {
|
||||
minimumSeverity: `${fieldIdPrefix}-minimum-severity`,
|
||||
};
|
||||
const instructionBoxClass =
|
||||
'mt-2 rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs leading-relaxed text-blue-900 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200';
|
||||
'mt-2 rounded-sm border border-blue-200 bg-blue-50 px-3 py-2 text-xs leading-relaxed text-blue-900/25 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200';
|
||||
|
||||
return (
|
||||
<div class="space-y-4 text-sm overflow-hidden">
|
||||
@@ -386,7 +386,7 @@ export function EmailProviderSelect(props: EmailProviderSelectProps) {
|
||||
type="button"
|
||||
onClick={props.onTest}
|
||||
disabled={props.testing || !props.config.enabled}
|
||||
class="min-h-11 rounded border border-blue-500 px-3 py-1.5 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0 dark:border-blue-400 dark:text-blue-300 dark:hover:bg-blue-900"
|
||||
class="min-h-11 rounded-sm border border-blue-500 px-3 py-1.5 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0 dark:border-blue-400 dark:text-blue-300 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
{getAlertEmailTestButtonLabel(Boolean(props.testing))}
|
||||
</button>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function IncidentAssistantHandoffButton(props: IncidentAssistantHandoffBu
|
||||
type="button"
|
||||
class={
|
||||
props.class ||
|
||||
'inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-surface-hover hover:text-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 dark:text-blue-400 dark:hover:text-blue-300'
|
||||
'inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-surface-hover hover:text-blue-700 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 dark:text-blue-400 dark:hover:text-blue-300'
|
||||
}
|
||||
title="Discuss this incident with Pulse Assistant"
|
||||
aria-label={`Discuss incident ${props.incident.id} with Pulse Assistant`}
|
||||
|
||||
@@ -43,10 +43,10 @@ export function IncidentTimelineEventCard(props: IncidentTimelineEventCardProps)
|
||||
<Show when={props.event.evidence}>
|
||||
{(evidence) => (
|
||||
<details class="text-xs text-muted mt-2">
|
||||
<summary class="cursor-pointer rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">
|
||||
<summary class="cursor-pointer rounded-sm focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2">
|
||||
{INCIDENT_EVIDENCE_DETAILS}
|
||||
</summary>
|
||||
<dl class="mt-2 grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 break-words">
|
||||
<dl class="mt-2 grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 wrap-break-word">
|
||||
<dt>Observed</dt>
|
||||
<dd>
|
||||
{formatIncidentEvidenceTime(evidence().observedAt) ?? INCIDENT_TIME_UNAVAILABLE}
|
||||
|
||||
@@ -163,7 +163,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
const baseButtonClass = `
|
||||
inline-flex items-center justify-center
|
||||
rounded-md transition-all duration-200
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500
|
||||
focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
`;
|
||||
|
||||
@@ -191,7 +191,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
onClick={handleOpenAssistant}
|
||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-base-content hover:bg-surface-hover"
|
||||
>
|
||||
<SparklesIcon class="h-4 w-4 flex-shrink-0 text-blue-600 dark:text-blue-400" />
|
||||
<SparklesIcon class="h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" />
|
||||
<span class="flex flex-col">
|
||||
<span class="font-medium">{t('alerts.assistant.explain.menuLabel')}</span>
|
||||
<span class="text-[10px] text-muted">{t('alerts.assistant.explain.menuHint')}</span>
|
||||
@@ -274,7 +274,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
<ShieldCheckIcon class="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<ShieldCheckIcon class="h-3.5 w-3.5 shrink-0" />
|
||||
</Show>
|
||||
<span class="text-xs font-medium">
|
||||
{usesPatrolPrimary()
|
||||
@@ -304,7 +304,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
bg-blue-600
|
||||
hover:bg-blue-700
|
||||
text-white font-medium
|
||||
shadow-sm hover:shadow-sm
|
||||
shadow-xs hover:shadow-xs
|
||||
gap-2
|
||||
${showPatrolOption() ? 'rounded-r-none' : ''}
|
||||
${isLocked() ? 'opacity-60 cursor-not-allowed hover:bg-blue-600' : ''}
|
||||
@@ -325,7 +325,7 @@ export function InvestigateAlertButton(props: InvestigateAlertButtonProps) {
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
<ShieldCheckIcon class="h-4 w-4 flex-shrink-0" />
|
||||
<ShieldCheckIcon class="h-4 w-4 shrink-0" />
|
||||
</Show>
|
||||
<span>
|
||||
{usesPatrolPrimary()
|
||||
|
||||
@@ -341,7 +341,7 @@ describe('ResourceTable', () => {
|
||||
expect(alertResourceTableMobileSource).toContain('FormTextarea');
|
||||
expect(alertResourceTableMobileSource).toContain('ActionIconButton');
|
||||
expect(alertResourceTableMobileSource).toContain(
|
||||
'min-h-11 w-full rounded border p-1 text-center text-sm sm:min-h-0',
|
||||
'min-h-11 w-full rounded-sm border p-1 text-center text-sm sm:min-h-0',
|
||||
);
|
||||
expect(alertResourceTableMobileSource).not.toContain(['<', 'textarea'].join(''));
|
||||
expect(alertResourceTableMobileSource).not.toContain(rawSvgTag);
|
||||
|
||||
@@ -145,14 +145,14 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</Show>
|
||||
|
||||
<Show when={selectedIds().size > 0 && props.onBulkEdit}>
|
||||
<div class="fixed bottom-8 left-1/2 -translate-x-1/2 bg-base border border-border shadow-2xl rounded-full px-5 py-3 flex items-center gap-6 z-[100] animate-in slide-in-from-bottom-5">
|
||||
<div class="fixed bottom-8 left-1/2 -translate-x-1/2 bg-base border border-border shadow-2xl rounded-full px-5 py-3 flex items-center gap-6 z-100 animate-in slide-in-from-bottom-5">
|
||||
<span class="text-sm font-medium text-white">
|
||||
{selectedIds().size} <span class="text-slate-400">selected</span>
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="bg-blue-600 hover:bg-blue-500 text-white rounded-full px-5 py-1.5 text-sm font-medium transition-colors shadow-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
class="bg-blue-600 hover:bg-blue-500 text-white rounded-full px-5 py-1.5 text-sm font-medium transition-colors shadow-xs focus:ring-2 focus:ring-blue-500 focus:outline-hidden"
|
||||
onClick={() => {
|
||||
if (props.onBulkEdit) {
|
||||
props.onBulkEdit(Array.from(selectedIds()));
|
||||
|
||||
@@ -95,37 +95,37 @@ export const CollapsibleSection: Component<CollapsibleSectionProps> = (props) =>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggle}
|
||||
class="flex min-h-11 w-full min-w-0 items-center gap-3 px-4 py-3 text-left cursor-pointer select-none focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-500"
|
||||
class="flex min-h-11 w-full min-w-0 items-center gap-3 px-4 py-3 text-left cursor-pointer select-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-500"
|
||||
aria-expanded={!isCollapsed()}
|
||||
aria-controls={`section-content-${props.id}`}
|
||||
>
|
||||
{/* Left side: Chevron + Icon + Title + Count */}
|
||||
<span class="flex-shrink-0 text-muted transition-transform duration-200">
|
||||
<span class="shrink-0 text-muted transition-transform duration-200">
|
||||
<Show when={isCollapsed()} fallback={<ChevronDown class="w-5 h-5" />}>
|
||||
<ChevronRight class="w-5 h-5" />
|
||||
</Show>
|
||||
</span>
|
||||
|
||||
<Show when={props.icon}>
|
||||
<span class="flex-shrink-0 text-muted">{props.icon}</span>
|
||||
<span class="shrink-0 text-muted">{props.icon}</span>
|
||||
</Show>
|
||||
|
||||
<span class="min-w-0">
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="truncate font-semibold text-base-content">{props.title}</span>
|
||||
<Show when={props.resourceCount !== undefined}>
|
||||
<span class="flex-shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-surface-alt text-muted">
|
||||
<span class="shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-surface-alt text-muted">
|
||||
{props.resourceCount}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.isGloballyDisabled}>
|
||||
<span class="flex-shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-400">
|
||||
<span class="shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-700 dark:bg-yellow-900/25 dark:text-yellow-400">
|
||||
{getAlertThresholdsSectionDisabledLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.hasChanges}>
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 rounded-full bg-blue-500"
|
||||
class="shrink-0 w-2 h-2 rounded-full bg-blue-500"
|
||||
title={getAlertThresholdsSectionUnsavedChangesTitle()}
|
||||
/>
|
||||
</Show>
|
||||
@@ -138,7 +138,7 @@ export const CollapsibleSection: Component<CollapsibleSectionProps> = (props) =>
|
||||
</h3>
|
||||
|
||||
<Show when={props.headerActions}>
|
||||
<div class="flex flex-shrink-0 items-center gap-2 pr-4">{props.headerActions}</div>
|
||||
<div class="flex shrink-0 items-center gap-2 pr-4">{props.headerActions}</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -197,9 +197,9 @@ export const SectionActionButton: Component<SectionActionButtonProps> = (props)
|
||||
const variantClasses = {
|
||||
default: 'text-slate-600 hover:text-base-content hover:bg-slate-100',
|
||||
primary:
|
||||
'text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:text-blue-400 dark:hover:text-blue-300 dark:hover:bg-blue-900',
|
||||
'text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:text-blue-400 dark:hover:text-blue-300 dark:hover:bg-blue-900/25',
|
||||
danger:
|
||||
'text-red-600 hover:text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900',
|
||||
'text-red-600 hover:text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900/25',
|
||||
};
|
||||
return (
|
||||
<button
|
||||
@@ -247,7 +247,7 @@ export const NestedGroupHeader: Component<NestedGroupHeaderProps> = (props) => {
|
||||
>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<Show when={props.onToggle}>
|
||||
<div class="flex-shrink-0 text-slate-400">
|
||||
<div class="shrink-0 text-slate-400">
|
||||
<Show when={props.collapsed} fallback={<ChevronDown class="w-4 h-4" />}>
|
||||
<ChevronRight class="w-4 h-4" />
|
||||
</Show>
|
||||
@@ -256,7 +256,7 @@ export const NestedGroupHeader: Component<NestedGroupHeaderProps> = (props) => {
|
||||
|
||||
<Show when={props.status}>
|
||||
<StatusDot
|
||||
class="flex-shrink-0"
|
||||
class="shrink-0"
|
||||
variant={statusIndicator().variant}
|
||||
title={statusIndicator().label}
|
||||
ariaLabel={statusIndicator().label}
|
||||
|
||||
@@ -89,11 +89,11 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
/>
|
||||
|
||||
<Show when={!state.helpBannerDismissed()}>
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900 p-3 relative group">
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/25 p-3 relative group">
|
||||
<button
|
||||
type="button"
|
||||
onClick={state.dismissHelpBanner}
|
||||
class="absolute right-1 top-1 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1 text-blue-500 transition-colors hover:bg-blue-100 hover:text-blue-700 dark:text-blue-400 dark:hover:bg-blue-900 dark:hover:text-blue-200 sm:right-2 sm:top-2 sm:min-h-0 sm:min-w-0 sm:opacity-0 sm:group-hover:opacity-100"
|
||||
class="absolute right-1 top-1 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1 text-blue-500 transition-colors hover:bg-blue-100 hover:text-blue-700 dark:text-blue-400 dark:hover:bg-blue-900/25 dark:hover:text-blue-200 sm:right-2 sm:top-2 sm:min-h-0 sm:min-w-0 sm:opacity-0 sm:group-hover:opacity-100"
|
||||
title={state.getAlertThresholdsHelpDismissLabel()}
|
||||
aria-label={state.getAlertThresholdsHelpDismissLabel()}
|
||||
>
|
||||
@@ -108,7 +108,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
</button>
|
||||
<div class="flex items-start gap-2 pr-6">
|
||||
<svg
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -120,7 +120,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="text-sm text-blue-900 dark:text-blue-100">
|
||||
<div class="text-sm text-blue-900/25 dark:text-blue-100">
|
||||
<p class="font-medium">{state.getAlertThresholdsHelpBanner().title}</p>
|
||||
<p class="mt-1">{state.getAlertThresholdsHelpBanner().toggleGuidance}</p>
|
||||
<p>{state.getAlertThresholdsHelpBanner().inheritanceGuidance}</p>
|
||||
@@ -152,12 +152,12 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
{(item) => (
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex min-h-11 items-center gap-2 rounded-full border border-sky-200 bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:border-sky-400 hover:bg-sky-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 sm:min-h-0 dark:border-sky-700 dark:hover:bg-sky-900"
|
||||
class="inline-flex min-h-11 items-center gap-2 rounded-full border border-sky-200 bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:border-sky-400 hover:bg-sky-100 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-sky-500 sm:min-h-0 dark:border-sky-700 dark:hover:bg-sky-900/25"
|
||||
onClick={() => state.revealCustomOverrides(item.key)}
|
||||
aria-label={`Show ${item.label}: ${state.getAlertThresholdsOverrideCountLabel(item.overrides)}`}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<span class="rounded-full bg-sky-100 px-2 py-0.5 text-sky-700 dark:bg-sky-900 dark:text-sky-200">
|
||||
<span class="rounded-full bg-sky-100 px-2 py-0.5 text-sky-700 dark:bg-sky-900/25 dark:text-sky-200">
|
||||
{state.getAlertThresholdsOverrideCountLabel(item.overrides)}
|
||||
</span>
|
||||
</button>
|
||||
@@ -173,7 +173,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={state.expandAll}
|
||||
class="min-h-11 rounded px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
class="min-h-11 rounded-sm px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
>
|
||||
Expand all
|
||||
</button>
|
||||
@@ -181,7 +181,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={state.collapseAll}
|
||||
class="min-h-11 rounded px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
class="min-h-11 rounded-sm px-2 py-1 text-xs transition-colors hover:bg-surface-hover hover:text-muted sm:min-h-0"
|
||||
>
|
||||
Collapse all
|
||||
</button>
|
||||
|
||||
@@ -105,7 +105,7 @@ export function ThresholdsTableAgentsResourcesSection(props: ThresholdsTableSect
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export function ThresholdsTableDockerIgnoredPrefixesSection(props: ThresholdsTab
|
||||
}}
|
||||
placeholder={state.dockerIgnoredPrefixesPresentation.placeholder}
|
||||
rows={4}
|
||||
textareaBaseClass="mt-4 w-full rounded-md border border-border bg-surface p-3 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
textareaBaseClass="mt-4 w-full rounded-md border border-border bg-surface p-3 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -57,7 +57,7 @@ export function ThresholdsTableDockerServiceGapSection(props: ThresholdsTableSec
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{state.dockerServicePresentation.warningGapDescription}
|
||||
@@ -85,7 +85,7 @@ export function ThresholdsTableDockerServiceGapSection(props: ThresholdsTableSec
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{state.dockerServicePresentation.criticalGapDescription}
|
||||
|
||||
@@ -80,7 +80,7 @@ export function ThresholdsTableDockerUpdateAlertsSection(props: ThresholdsTableS
|
||||
}));
|
||||
tableProps.setHasUnsavedChanges(true);
|
||||
}}
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
class="mt-1 w-full rounded-md border border-border bg-surface p-2 text-sm text-base-content focus:border-sky-500 focus:outline-hidden focus:ring-2 focus:ring-sky-200 dark:focus:border-sky-400 dark:focus:ring-sky-600"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted">{state.dockerUpdatePresentation.delayDescription}</p>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +100,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
onClick={() => props.selectService(service.id)}
|
||||
class={`min-h-11 px-2 py-1.5 text-left border transition-colors text-xs sm:min-h-0 ${
|
||||
props.formData().service === service.id
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/25'
|
||||
: 'border-border hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
@@ -114,7 +114,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
|
||||
<Show when={props.currentTemplate()?.instructions}>
|
||||
<div class="mb-3 border-l-2 border-blue-300 pl-3 text-xs leading-relaxed text-blue-800 dark:border-blue-700 dark:text-blue-200">
|
||||
<h4 class="text-sm font-medium text-blue-900 dark:text-blue-100 mb-2">
|
||||
<h4 class="text-sm font-medium text-blue-900/25 dark:text-blue-100 mb-2">
|
||||
{getAlertWebhookSetupInstructionsTitle()}
|
||||
</h4>
|
||||
{props.currentTemplate()!.instructions}
|
||||
@@ -411,7 +411,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
<div class="flex justify-end gap-2 text-xs">
|
||||
<button
|
||||
onClick={props.cancelForm}
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded text-xs hover:bg-surface-hover sm:min-h-0"
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded-sm text-xs hover:bg-surface-hover sm:min-h-0"
|
||||
>
|
||||
{ALERT_WEBHOOK_CANCEL_LABEL}
|
||||
</button>
|
||||
@@ -419,7 +419,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
<button
|
||||
onClick={props.testWebhookForm}
|
||||
disabled={props.testing === (props.editingId() || 'temp-new-webhook')}
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded text-xs hover:bg-slate-100 sm:min-h-0"
|
||||
class="min-h-11 px-3 py-1.5 border border-border rounded-sm text-xs hover:bg-slate-100 sm:min-h-0"
|
||||
>
|
||||
{getAlertWebhookTestLabel(
|
||||
props.testing === (props.editingId() || 'temp-new-webhook'),
|
||||
@@ -430,7 +430,7 @@ export function WebhookConfigForm(props: WebhookConfigFormProps) {
|
||||
<button
|
||||
onClick={props.saveWebhook}
|
||||
disabled={!props.formData().name || !props.formData().url}
|
||||
class="min-h-11 px-3 py-1.5 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0"
|
||||
class="min-h-11 px-3 py-1.5 bg-blue-600 text-white text-xs rounded-sm hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed sm:min-h-0"
|
||||
>
|
||||
{getAlertWebhookSubmitLabel(Boolean(props.editingId()))}
|
||||
</button>
|
||||
|
||||
@@ -29,7 +29,7 @@ interface WebhookConfigListProps {
|
||||
export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
return (
|
||||
<div class="space-y-3 w-full">
|
||||
<div class="flex flex-col gap-2 rounded border border-border px-3 py-3 text-xs sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex flex-col gap-2 rounded-sm border border-border px-3 py-3 text-xs sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="text-muted sm:text-sm">
|
||||
{getAlertWebhookSummaryLabel(
|
||||
props.webhooks.filter((webhook) => webhook.enabled).length,
|
||||
@@ -40,14 +40,14 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
<button
|
||||
onClick={() => props.toggleAllWebhooks(false)}
|
||||
disabled={!props.someEnabled()}
|
||||
class="min-h-11 w-full rounded border px-3 py-1 text-xs transition-colors hover:bg-surface-hover sm:min-h-0 sm:w-auto"
|
||||
class="min-h-11 w-full rounded-sm border px-3 py-1 text-xs transition-colors hover:bg-surface-hover sm:min-h-0 sm:w-auto"
|
||||
>
|
||||
{getAlertWebhookToggleAllLabel(false)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.toggleAllWebhooks(true)}
|
||||
disabled={props.allEnabled()}
|
||||
class="min-h-11 w-full rounded border border-green-500 px-3 py-1 text-xs text-green-700 transition-colors hover:bg-green-50 sm:min-h-0 sm:w-auto dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900"
|
||||
class="min-h-11 w-full rounded-sm border border-green-500 px-3 py-1 text-xs text-green-700 transition-colors hover:bg-green-50 sm:min-h-0 sm:w-auto dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900/25"
|
||||
>
|
||||
{getAlertWebhookToggleAllLabel(true)}
|
||||
</button>
|
||||
@@ -61,22 +61,22 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
<span class="font-medium text-base-content">{webhook.name}</span>
|
||||
<button
|
||||
onClick={() => props.onToggleWebhook(webhook)}
|
||||
class={`min-h-11 rounded border px-3 py-1 text-xs font-medium transition-colors sm:min-h-0 ${webhook.enabled ? 'border-green-500 text-green-700 hover:bg-green-50 dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
class={`min-h-11 rounded-sm border px-3 py-1 text-xs font-medium transition-colors sm:min-h-0 ${webhook.enabled ? 'border-green-500 text-green-700 hover:bg-green-50 dark:border-green-600 dark:text-green-400 dark:hover:bg-green-900/25' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
{getAlertWebhookToggleLabel(webhook.enabled)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-2 text-[11px] text-muted sm:text-xs">
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{getAlertWebhookServiceLabelFromTemplates(
|
||||
webhook.service || 'generic',
|
||||
props.templates(),
|
||||
)}
|
||||
</span>
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{webhook.method}
|
||||
</span>
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{getAlertDestinationSeverityLabel(
|
||||
webhook.minimumSeverity === 'critical' || webhook.minimumSeverity === 'warning'
|
||||
? webhook.minimumSeverity
|
||||
@@ -85,13 +85,13 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
</span>
|
||||
<For each={webhook.tagFilter ?? []}>
|
||||
{(tag) => (
|
||||
<span class="rounded bg-sky-50 px-2 py-0.5 text-sky-700 dark:bg-sky-900 dark:text-sky-300">
|
||||
<span class="rounded-sm bg-sky-50 px-2 py-0.5 text-sky-700 dark:bg-sky-900/25 dark:text-sky-300">
|
||||
{tag}
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
<Show when={(webhook.tagFilter?.length ?? 0) > 1}>
|
||||
<span class="rounded bg-surface-alt px-2 py-0.5 text-muted">
|
||||
<span class="rounded-sm bg-surface-alt px-2 py-0.5 text-muted">
|
||||
Match {webhook.tagFilterMode === 'any' ? 'any' : 'all'}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -101,19 +101,19 @@ export function WebhookConfigList(props: WebhookConfigListProps) {
|
||||
<button
|
||||
onClick={() => props.onTestWebhook(webhook)}
|
||||
disabled={props.testing === webhook.id || !webhook.enabled}
|
||||
class="min-h-11 rounded border px-3 py-1 text-xs text-base-content transition-colors hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 rounded-sm border px-3 py-1 text-xs text-base-content transition-colors hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
{getAlertWebhookTestLabel(props.testing === webhook.id)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.onEditWebhook(webhook)}
|
||||
class="min-h-11 rounded border border-blue-300 px-3 py-1 text-xs text-blue-600 transition-colors hover:bg-blue-50 sm:min-h-0 dark:border-blue-500 dark:text-blue-300 dark:hover:bg-blue-900"
|
||||
class="min-h-11 rounded-sm border border-blue-300 px-3 py-1 text-xs text-blue-600 transition-colors hover:bg-blue-50 sm:min-h-0 dark:border-blue-500 dark:text-blue-300 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
{ALERT_WEBHOOK_EDIT_LABEL}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.onDeleteWebhook(webhook)}
|
||||
class="min-h-11 rounded border border-red-300 px-3 py-1 text-xs text-red-600 transition-colors hover:bg-red-50 sm:min-h-0 dark:border-red-500 dark:text-red-300 dark:hover:bg-red-900"
|
||||
class="min-h-11 rounded-sm border border-red-300 px-3 py-1 text-xs text-red-600 transition-colors hover:bg-red-50 sm:min-h-0 dark:border-red-500 dark:text-red-300 dark:hover:bg-red-900/25"
|
||||
>
|
||||
{ALERT_WEBHOOK_DELETE_LABEL}
|
||||
</button>
|
||||
|
||||
@@ -33,7 +33,7 @@ export const AppBootstrapStatus: Component<AppBootstrapStatusProps> = (props) =>
|
||||
class="flex min-h-screen items-center justify-center bg-base px-4 py-8 text-base-content"
|
||||
aria-labelledby="app-bootstrap-title"
|
||||
>
|
||||
<div class="w-full max-w-md rounded-md border border-border bg-surface p-6 text-center shadow-sm sm:p-8">
|
||||
<div class="w-full max-w-md rounded-md border border-border bg-surface p-6 text-center shadow-xs sm:p-8">
|
||||
<LoadingSpinner size="xl" tone="info" class="mb-4" />
|
||||
<h1 id="app-bootstrap-title" class="text-lg font-semibold tracking-tight">
|
||||
Connecting to Pulse
|
||||
|
||||
@@ -37,7 +37,7 @@ export const PulseBrandMark: Component<PulseBrandMarkProps> = (props) => {
|
||||
</Show>
|
||||
<circle class="pulse-bg fill-blue-600 dark:fill-blue-500" cx="128" cy="128" r="122" />
|
||||
<circle
|
||||
class="pulse-ring fill-none stroke-white dark:stroke-[#dbeafe] stroke-[14] opacity-[0.92]"
|
||||
class="pulse-ring fill-none stroke-white dark:stroke-[#dbeafe] stroke-14 opacity-[0.92]"
|
||||
cx="128"
|
||||
cy="128"
|
||||
r="84"
|
||||
|
||||
@@ -222,13 +222,13 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<Show when={proposal()}>
|
||||
{(current) => (
|
||||
<section
|
||||
class="rounded-lg border border-blue-300 bg-blue-50/70 p-4 shadow-sm dark:border-blue-800 dark:bg-blue-950/30"
|
||||
class="rounded-lg border border-blue-300 bg-blue-50/70 p-4 shadow-xs dark:border-blue-800 dark:bg-blue-950/30"
|
||||
aria-label="Suggested service verification"
|
||||
>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="text-sm font-semibold text-blue-950 dark:text-blue-100">
|
||||
<h3 class="text-sm font-semibold text-blue-950/25 dark:text-blue-100">
|
||||
Suggested service verification
|
||||
</h3>
|
||||
<span class="rounded-full border border-blue-300 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-blue-700 dark:border-blue-700 dark:text-blue-300">
|
||||
@@ -274,7 +274,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
}
|
||||
>
|
||||
<div class="mt-4 grid gap-2 sm:grid-cols-2">
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900 dark:bg-black/10">
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900/25 dark:bg-black/10">
|
||||
<p class="text-[10px] font-medium uppercase tracking-wide text-blue-600 dark:text-blue-400">
|
||||
Endpoint · inferred
|
||||
</p>
|
||||
@@ -282,7 +282,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
{endpointLabel(current())}
|
||||
</code>
|
||||
</div>
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900 dark:bg-black/10">
|
||||
<div class="rounded-md border border-blue-200 bg-white/70 p-3 dark:border-blue-900/25 dark:bg-black/10">
|
||||
<p class="text-[10px] font-medium uppercase tracking-wide text-blue-600 dark:text-blue-400">
|
||||
Expected behavior · reviewed default
|
||||
</p>
|
||||
@@ -344,7 +344,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<Show when={duplicate()}>
|
||||
{(match) => (
|
||||
<div
|
||||
class="mt-3 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-200"
|
||||
class="mt-3 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900/25 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-200"
|
||||
role="status"
|
||||
>
|
||||
{match().kind === 'endpoint'
|
||||
@@ -359,7 +359,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={targets.error}>
|
||||
<div class="mt-3 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-xs text-red-900 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200">
|
||||
<div class="mt-3 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-xs text-red-900/25 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200">
|
||||
<p role="alert">
|
||||
Pulse could not check for existing active checks. Creating a check is unavailable
|
||||
until that safety check succeeds.
|
||||
@@ -380,8 +380,8 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<div
|
||||
class={`mt-3 rounded-md border px-3 py-2 text-xs ${
|
||||
result().success
|
||||
? 'border-emerald-300 bg-emerald-50 text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200'
|
||||
: 'border-red-300 bg-red-50 text-red-900 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200'
|
||||
? 'border-emerald-300 bg-emerald-50 text-emerald-900/25 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200'
|
||||
: 'border-red-300 bg-red-50 text-red-900/25 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200'
|
||||
}`}
|
||||
role={result().success ? 'status' : 'alert'}
|
||||
>
|
||||
@@ -392,7 +392,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
<Show when={createdTarget()}>
|
||||
{(created) => (
|
||||
<div
|
||||
class="mt-3 flex items-center gap-2 rounded-md border border-emerald-300 bg-emerald-50 px-3 py-2 text-xs text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200"
|
||||
class="mt-3 flex items-center gap-2 rounded-md border border-emerald-300 bg-emerald-50 px-3 py-2 text-xs text-emerald-900/25 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-200"
|
||||
role="status"
|
||||
>
|
||||
<CheckCircleIcon class="h-4 w-4" aria-hidden="true" />
|
||||
@@ -478,7 +478,7 @@ export const AvailabilityProposalCard: Component<AvailabilityProposalCardProps>
|
||||
</Show>
|
||||
<Show when={machineDiscoveries.error}>
|
||||
<p
|
||||
class="rounded-md border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"
|
||||
class="rounded-md border border-red-300 bg-red-50 p-4 text-sm text-red-900/25 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"
|
||||
role="alert"
|
||||
>
|
||||
Pulse could not load this machine’s assurance suggestions. Close this review and
|
||||
|
||||
@@ -148,9 +148,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
return (
|
||||
<div class="space-y-4 whitespace-normal">
|
||||
<Show when={discoveryFeatureKnownDisabled()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50/80 p-3 shadow-sm dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50/80 p-3 shadow-xs dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<div class="text-xs text-amber-800 dark:text-amber-200">
|
||||
<p class="mb-1 font-medium">Service identification is off</p>
|
||||
<p class="text-amber-700 dark:text-amber-300">
|
||||
@@ -211,9 +211,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
Surfaces the "enabled but no provider" dead end that previously only
|
||||
showed for agents, so Discovery is never silently on-but-useless. */}
|
||||
<Show when={!discoveryInfo.loading && discoveryReadiness().status === 'needs_ai_provider'}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50/80 p-3 shadow-sm dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50/80 p-3 shadow-xs dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<div class="text-xs text-amber-800 dark:text-amber-200">
|
||||
<p class="mb-1 font-medium">AI provider not configured</p>
|
||||
<p class="text-amber-700 dark:text-amber-300">
|
||||
@@ -236,7 +236,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
type="button"
|
||||
onClick={() => handleTriggerDiscovery(true)}
|
||||
disabled={isScanning() || !canTriggerDiscovery()}
|
||||
class="inline-flex min-h-9 items-center justify-center gap-1.5 rounded border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="inline-flex min-h-9 items-center justify-center gap-1.5 rounded-sm border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<Show
|
||||
when={isScanning()}
|
||||
@@ -269,11 +269,11 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* "What Discovery Does" explanation - shown when no discovery exists yet */}
|
||||
<Show when={!discovery() && !isScanning() && !showLoadingSpinner() && showExplanation()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50 p-3 shadow-sm dark:border-amber-800 dark:bg-amber-900">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50 p-3 shadow-xs dark:border-amber-800 dark:bg-amber-900/25">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<svg
|
||||
class="w-4 h-4 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5"
|
||||
class="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -292,7 +292,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowExplanation(false)}
|
||||
class="text-amber-500 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300 flex-shrink-0"
|
||||
class="text-amber-500 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300 shrink-0"
|
||||
title="Dismiss"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -320,7 +320,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
}
|
||||
>
|
||||
<details
|
||||
class="rounded border border-border bg-surface shadow-sm"
|
||||
class="rounded-sm border border-border bg-surface shadow-xs"
|
||||
open={showCommandsPreview()}
|
||||
>
|
||||
<summary
|
||||
@@ -351,7 +351,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
{(cmd) => (
|
||||
<div class="text-xs">
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="text-[10px] px-1.5 py-0.5 rounded bg-surface-hover text-base-content font-mono break-all">
|
||||
<code class="text-[10px] px-1.5 py-0.5 rounded-sm bg-surface-hover text-base-content font-mono break-all">
|
||||
{cmd.command}
|
||||
</code>
|
||||
</div>
|
||||
@@ -374,7 +374,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Scan Progress Bar */}
|
||||
<Show when={scanProgress() && isScanning()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 p-3 shadow-sm dark:border-blue-800 dark:bg-blue-900">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 p-3 shadow-xs dark:border-blue-800 dark:bg-blue-900/25">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full"></div>
|
||||
@@ -409,7 +409,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Scanning state without WebSocket progress - show live timer */}
|
||||
<Show when={isScanning() && !scanProgress()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 p-3 shadow-sm dark:border-blue-800 dark:bg-blue-900">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 p-3 shadow-xs dark:border-blue-800 dark:bg-blue-900/25">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full"></div>
|
||||
<span class="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
@@ -430,7 +430,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
class="mb-4 rounded-md border border-green-200 bg-green-50 p-4 dark:border-green-800 dark:bg-green-900"
|
||||
class="mb-4 rounded-md border border-green-200 bg-green-50 p-4 dark:border-green-800 dark:bg-green-900/25"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
@@ -455,10 +455,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Scan Error */}
|
||||
<Show when={scanError()}>
|
||||
<div class="mb-4 rounded-md border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900">
|
||||
<div class="mb-4 rounded-md border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/25">
|
||||
<div class="flex items-start gap-3">
|
||||
<svg
|
||||
class="w-5 h-5 text-red-500 dark:text-red-400 flex-shrink-0 mt-0.5"
|
||||
class="w-5 h-5 text-red-500 dark:text-red-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -535,10 +535,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
}
|
||||
>
|
||||
<Show when={discoveryReadiness().status === 'needs_commands'}>
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900">
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900/25">
|
||||
<div class="flex items-start gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 flex-shrink-0 mt-0.5"
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -566,10 +566,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={discoveryReadiness().status === 'needs_connected_agent'}>
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900">
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-amber-200 bg-amber-50 p-3 text-left dark:border-amber-800 dark:bg-amber-900/25">
|
||||
<div class="flex items-start gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 flex-shrink-0 mt-0.5"
|
||||
class="w-4 h-4 text-amber-500 dark:text-amber-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -588,7 +588,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<p class="text-amber-700 dark:text-amber-300 mt-0.5">
|
||||
Commands are enabled, but the agent isn't connected via WebSocket. Check
|
||||
that the API token has the{' '}
|
||||
<code class="px-1 py-0.5 bg-amber-100 dark:bg-amber-800 rounded">
|
||||
<code class="px-1 py-0.5 bg-amber-100 dark:bg-amber-800 rounded-sm">
|
||||
agent:exec
|
||||
</code>{' '}
|
||||
scope in{' '}
|
||||
@@ -606,10 +606,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
green "connected" claim must require a genuinely connected
|
||||
agent with commands explicitly enabled. */}
|
||||
<Show when={props.commandsEnabled === true && hasConnectedAgent()}>
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-green-200 bg-green-50 p-3 text-left dark:border-green-800 dark:bg-green-900">
|
||||
<div class="mb-4 mx-auto max-w-md rounded-md border border-green-200 bg-green-50 p-3 text-left dark:border-green-800 dark:bg-green-900/25">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-green-500 dark:text-green-400 flex-shrink-0"
|
||||
class="w-4 h-4 text-green-500 dark:text-green-400 shrink-0"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -728,9 +728,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
{(d) => (
|
||||
<div class="space-y-4">
|
||||
<Show when={isStaleDiscovery()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50/80 p-3 shadow-sm dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50/80 p-3 shadow-xs dark:border-amber-800/50 dark:bg-amber-900/20">
|
||||
<div class="flex items-start gap-2">
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<TriangleAlertIcon class="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<p class="text-xs text-amber-800 dark:text-amber-200">
|
||||
This was discovered by an earlier version of Pulse. Re-run discovery for
|
||||
improved identification and access details.
|
||||
@@ -781,11 +781,11 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
("Last run: X ago") whenever it's rendered; only repeat it
|
||||
here when that block is absent. */}
|
||||
<Show when={d().updated_at && !showManualRunAction()}>
|
||||
<span class="rounded border border-border bg-surface-alt px-2 py-0.5">
|
||||
<span class="rounded-sm border border-border bg-surface-alt px-2 py-0.5">
|
||||
Last observed {formatDiscoveryAge(d().updated_at)}
|
||||
</span>
|
||||
</Show>
|
||||
<span class="rounded border border-border bg-surface-alt px-2 py-0.5">
|
||||
<span class="rounded-sm border border-border bg-surface-alt px-2 py-0.5">
|
||||
Available to Pulse Assistant
|
||||
</span>
|
||||
</div>
|
||||
@@ -863,7 +863,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* CLI Access */}
|
||||
<Show when={d().cli_access}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>CLI Access</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -899,7 +899,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
d().log_paths?.length > 0
|
||||
}
|
||||
>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<Show when={d().config_paths?.length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="mb-1 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
@@ -967,7 +967,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
Docker resources. The host source is where to actually edit or
|
||||
back up files; the container destination is what the app sees. */}
|
||||
<Show when={(d().docker_mounts?.length ?? 0) > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Bind Mounts</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -997,7 +997,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Ports */}
|
||||
<Show when={d().ports?.length > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Listening Ports</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -1028,7 +1028,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Key Facts */}
|
||||
<Show when={d().facts?.length > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Discovered Facts</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
@@ -1069,7 +1069,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
{/* User Notes */}
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="rounded-sm border border-border p-3 shadow-xs">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
Your Notes
|
||||
@@ -1104,7 +1104,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
value={notesText()}
|
||||
onInput={(e) => setNotesText(e.currentTarget.value)}
|
||||
placeholder="Add notes about this resource (API tokens, passwords, important info)..."
|
||||
class="w-full h-24 px-2 py-1.5 text-xs border border-border rounded bg-surface text-base-content focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full h-24 px-2 py-1.5 text-xs border border-border rounded-sm bg-surface text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
<Show when={saveError()}>
|
||||
<p
|
||||
@@ -1118,13 +1118,13 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onClick={handleSaveNotes}
|
||||
class="px-3 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors"
|
||||
class="px-3 py-1 bg-blue-600 text-white text-xs rounded-sm hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingNotes(false)}
|
||||
class="px-3 py-1 bg-surface-hover text-base-content text-xs rounded hover:bg-slate-300 transition-colors"
|
||||
class="px-3 py-1 bg-surface-hover text-base-content text-xs rounded-sm hover:bg-slate-300 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1135,7 +1135,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
|
||||
{/* Analysis reasoning (collapsible) */}
|
||||
<Show when={d().ai_reasoning}>
|
||||
<details class="rounded border shadow-sm">
|
||||
<details class="rounded-sm border shadow-xs">
|
||||
<summary class="p-3 text-[11px] font-medium uppercase tracking-wide text-base-content cursor-pointer hover:bg-surface-hover">
|
||||
{DISCOVERY_ANALYSIS_REASONING_LABEL}
|
||||
</summary>
|
||||
@@ -1149,7 +1149,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<Show
|
||||
when={d().raw_command_output && Object.keys(d().raw_command_output!).length > 0}
|
||||
>
|
||||
<details class="rounded border shadow-sm">
|
||||
<details class="rounded-sm border shadow-xs">
|
||||
<summary class="p-3 text-[11px] font-medium uppercase tracking-wide text-base-content cursor-pointer hover:bg-surface-hover">
|
||||
Scan Details ({Object.keys(d().raw_command_output!).length} commands)
|
||||
</summary>
|
||||
@@ -1158,7 +1158,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
{([cmdName, output]) => (
|
||||
<div>
|
||||
<div class="text-xs font-medium text-base-content mb-1">{cmdName}</div>
|
||||
<pre class="text-[10px] bg-surface-alt rounded p-2 overflow-x-auto text-muted max-h-32 overflow-y-auto">
|
||||
<pre class="text-[10px] bg-surface-alt rounded-sm p-2 overflow-x-auto text-muted max-h-32 overflow-y-auto">
|
||||
{output || '(no output)'}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -1192,7 +1192,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
||||
<button
|
||||
onClick={() => handleTriggerDiscovery(true)}
|
||||
disabled={isScanning() || !canTriggerDiscovery()}
|
||||
class="px-3 py-1.5 bg-surface-hover text-base-content text-xs rounded hover:bg-surface-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-1.5"
|
||||
class="px-3 py-1.5 bg-surface-hover text-base-content text-xs rounded-sm hover:bg-surface-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-1.5"
|
||||
>
|
||||
<Show
|
||||
when={isScanning()}
|
||||
|
||||
@@ -199,12 +199,12 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="w-[12rem]">
|
||||
<div class="w-48">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder={drawerPresentation.searchPlaceholder}
|
||||
inputClass="py-1 text-xs font-medium shadow-sm"
|
||||
inputClass="py-1 text-xs font-medium shadow-xs"
|
||||
typeToSearch
|
||||
clearOnEscape
|
||||
/>
|
||||
@@ -227,17 +227,17 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
>
|
||||
<div class="mt-2 flex flex-wrap gap-2 text-[11px]">
|
||||
<Show when={asTrimmedString(swarm()?.nodeRole)}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{formatSwarmRoleLabel(asTrimmedString(swarm()?.nodeRole))}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={asTrimmedString(swarm()?.localState)}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{formatSwarmStateLabel(asTrimmedString(swarm()?.localState))}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={typeof swarm()?.controlAvailable === 'boolean'}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-2 py-0.5 text-base-content">
|
||||
{formatSwarmControlLabel(swarm()?.controlAvailable)}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -245,7 +245,7 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
</Show>
|
||||
|
||||
<Show when={asTrimmedString(swarm()?.error)}>
|
||||
<div class="mt-2 rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-800 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="mt-2 rounded-sm border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-800 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
{asTrimmedString(swarm()?.error)}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -15,7 +15,7 @@ interface ErrorBoundaryProps {
|
||||
const DefaultErrorFallback: Component<{ error: Error; reset: () => void }> = (props) => {
|
||||
return (
|
||||
<div class="min-h-screen flex items-center justify-center bg-base p-4">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-sm p-6">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-xs p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<AlertTriangleIcon class="mr-3 h-12 w-12 text-red-500" aria-hidden="true" />
|
||||
<div>
|
||||
@@ -91,7 +91,7 @@ export const RouteErrorBoundary: Component<{ children: JSX.Element }> = (props)
|
||||
<ErrorBoundary
|
||||
fallback={(_error, reset) => (
|
||||
<div class="flex min-h-[60vh] items-center justify-center p-4">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-sm p-6">
|
||||
<div class="max-w-md w-full bg-surface rounded-md shadow-xs p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<AlertTriangleIcon class="mr-3 h-10 w-10 text-red-500" aria-hidden="true" />
|
||||
<SectionHeader
|
||||
|
||||
@@ -108,7 +108,7 @@ export function GitHubStarBanner() {
|
||||
return (
|
||||
<Show when={showPrompt()}>
|
||||
<section
|
||||
class="fixed left-4 right-20 bottom-[var(--pulse-mobile-nav-height)] z-30 max-w-sm overflow-hidden rounded-lg border border-border bg-surface text-base-content shadow-lg md:right-auto md:bottom-4"
|
||||
class="fixed left-4 right-20 bottom-(--pulse-mobile-nav-height) z-30 max-w-sm overflow-hidden rounded-lg border border-border bg-surface text-base-content shadow-lg md:right-auto md:bottom-4"
|
||||
aria-labelledby="github-star-title"
|
||||
aria-live="polite"
|
||||
>
|
||||
|
||||
@@ -152,13 +152,13 @@ export function AvailabilityProbeStatusCard(props: AvailabilityProbeStatusCardPr
|
||||
<InfoCardFrame data-testid="availability-probe-status">
|
||||
<div class="flex items-center justify-between gap-2 mb-2">
|
||||
<div class="flex min-w-0 items-center gap-1.5">
|
||||
<Activity class="h-3.5 w-3.5 text-base-content/60" aria-hidden="true" />
|
||||
<Activity class="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<h3 class="truncate text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
Availability
|
||||
</h3>
|
||||
</div>
|
||||
<span
|
||||
class="shrink-0 rounded px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
class="shrink-0 rounded-sm px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
classList={{
|
||||
'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300':
|
||||
isFreshUp(),
|
||||
@@ -215,7 +215,7 @@ export function AvailabilityProbeStatusCard(props: AvailabilityProbeStatusCardPr
|
||||
</div>
|
||||
<For each={props.availability.locations}>
|
||||
{(location) => (
|
||||
<div class="flex items-start justify-between gap-3 rounded bg-base-200/60 px-2 py-1.5">
|
||||
<div class="flex items-start justify-between gap-3 rounded-sm bg-base-200/60 px-2 py-1.5">
|
||||
<div class="min-w-0">
|
||||
<div
|
||||
class="truncate font-medium text-base-content"
|
||||
@@ -253,11 +253,7 @@ export function AvailabilityProbeStatusCard(props: AvailabilityProbeStatusCardPr
|
||||
valueTitle={targetAddr()}
|
||||
/>
|
||||
<Show when={lastChecked()}>
|
||||
<InfoCardKeyValueRow
|
||||
label="Checked"
|
||||
value={lastChecked()}
|
||||
valueClass="text-base-content/70"
|
||||
/>
|
||||
<InfoCardKeyValueRow label="Checked" value={lastChecked()} valueClass="" />
|
||||
</Show>
|
||||
<InfoCardKeyValueRow
|
||||
label="Freshness"
|
||||
|
||||
@@ -62,7 +62,7 @@ export const ClusterDeployBanner: Component<ClusterDeployBannerProps> = (props)
|
||||
e.stopPropagation();
|
||||
props.onDeploy(info().cluster, info().cluster);
|
||||
}}
|
||||
class="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-medium text-blue-700 bg-blue-100 hover:bg-blue-200 dark:text-blue-300 dark:bg-blue-900 dark:hover:bg-blue-800 transition-colors"
|
||||
class="inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-[10px] font-medium text-blue-700 bg-blue-100 hover:bg-blue-200 dark:text-blue-300 dark:bg-blue-900/25 dark:hover:bg-blue-800 transition-colors"
|
||||
>
|
||||
<RocketIcon class="w-2.5 h-2.5" />
|
||||
Review & Deploy
|
||||
|
||||
@@ -42,9 +42,9 @@ const STATUS_LABELS: Record<MaintenanceVerificationStatus, string> = {
|
||||
|
||||
const STATUS_CLASSES: Record<MaintenanceVerificationStatus, string> = {
|
||||
pending: 'bg-surface-hover text-base-content',
|
||||
healthy: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300',
|
||||
needs_review: 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300',
|
||||
failed_verification: 'bg-rose-100 text-rose-700 dark:bg-rose-900 dark:text-rose-300',
|
||||
healthy: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/25 dark:text-emerald-300',
|
||||
needs_review: 'bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300',
|
||||
failed_verification: 'bg-rose-100 text-rose-700 dark:bg-rose-900/25 dark:text-rose-300',
|
||||
};
|
||||
|
||||
export const MaintenanceVerificationSection: Component<MaintenanceVerificationSectionProps> = (
|
||||
@@ -111,7 +111,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
|
||||
return (
|
||||
<section
|
||||
class="rounded border border-border bg-surface p-3 text-sm"
|
||||
class="rounded-sm border border-border bg-surface p-3 text-sm"
|
||||
data-testid="maintenance-verification-section"
|
||||
aria-labelledby="maintenance-verification-heading"
|
||||
>
|
||||
@@ -127,7 +127,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-border bg-surface-hover px-2 py-1 text-xs font-medium hover:bg-surface"
|
||||
class="rounded-sm border border-border bg-surface-hover px-2 py-1 text-xs font-medium hover:bg-surface"
|
||||
disabled={rerunning() || !props.resourceId}
|
||||
onClick={handleRerun}
|
||||
data-testid="maintenance-verification-rerun"
|
||||
@@ -139,7 +139,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
<Show
|
||||
when={hasReports()}
|
||||
fallback={
|
||||
<div class="mt-3 rounded border border-dashed border-border bg-surface-hover p-3 text-xs text-muted">
|
||||
<div class="mt-3 rounded-sm border border-dashed border-border bg-surface-hover p-3 text-xs text-muted">
|
||||
No verification reports yet. A report is written automatically the next time this
|
||||
resource exits a maintenance window.
|
||||
</div>
|
||||
@@ -149,13 +149,13 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
<For each={reports()}>
|
||||
{(report) => (
|
||||
<li
|
||||
class="rounded border border-border bg-surface-hover p-2"
|
||||
class="rounded-sm border border-border bg-surface-hover p-2"
|
||||
data-testid="maintenance-verification-report"
|
||||
>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
class={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium ${STATUS_CLASSES[report.status]}`}
|
||||
class={`inline-flex items-center rounded-sm px-1.5 py-0.5 text-[10px] font-medium ${STATUS_CLASSES[report.status]}`}
|
||||
data-testid={`maintenance-verification-status-${report.status}`}
|
||||
>
|
||||
{STATUS_LABELS[report.status]}
|
||||
@@ -168,7 +168,7 @@ export const MaintenanceVerificationSection: Component<MaintenanceVerificationSe
|
||||
<Show when={!report.userOutcome}>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-border bg-surface px-2 py-1 text-[11px] font-medium hover:bg-surface-hover"
|
||||
class="rounded-sm border border-border bg-surface px-2 py-1 text-[11px] font-medium hover:bg-surface-hover"
|
||||
disabled={reviewingId() === report.id}
|
||||
onClick={() => handleReview(report)}
|
||||
data-testid="maintenance-verification-review"
|
||||
|
||||
@@ -28,7 +28,7 @@ export const ReportMergeModal: Component<ReportMergeModalProps> = (props) => {
|
||||
label: badge?.label || getSourcePlatformLabel(source),
|
||||
classes:
|
||||
badge?.classes ||
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-base-content',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-base-content',
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -125,7 +125,7 @@ export const ReportMergeModal: Component<ReportMergeModalProps> = (props) => {
|
||||
value={notes()}
|
||||
onInput={(event) => setNotes(event.currentTarget.value)}
|
||||
rows={3}
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
placeholder="Example: Agent running on a different host with same hostname."
|
||||
/>
|
||||
|
||||
@@ -133,7 +133,7 @@ export const ReportMergeModal: Component<ReportMergeModalProps> = (props) => {
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900 dark:bg-red-900 dark:text-red-200"
|
||||
class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900/25 dark:bg-red-900/25 dark:text-red-200"
|
||||
>
|
||||
{error()}
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
const compensation = () => props.audit.result?.actionResultV2?.compensation;
|
||||
|
||||
return (
|
||||
<div class="rounded border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="rounded-sm border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-medium text-base-content">
|
||||
@@ -46,7 +46,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<span class={`shrink-0 rounded border px-1.5 py-0.5 font-medium ${state().className}`}>
|
||||
<span class={`shrink-0 rounded-sm border px-1.5 py-0.5 font-medium ${state().className}`}>
|
||||
{state().label}
|
||||
</span>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
<InfoCardKeyValueRow
|
||||
label="Intent"
|
||||
value={preflight()?.intendedChange}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={(preflight()?.safetyChecks || []).length > 0}>
|
||||
@@ -86,7 +86,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(() => {
|
||||
const presentation = resultPresentation()!;
|
||||
return (
|
||||
<div class={`rounded border px-2 py-1 text-[10px] ${presentation.className}`}>
|
||||
<div class={`rounded-sm border px-2 py-1 text-[10px] ${presentation.className}`}>
|
||||
<div class="font-medium">{presentation.label}</div>
|
||||
<Show when={presentation.kind === 'refusal'}>
|
||||
<div class="mt-0.5 font-medium">Refused before dispatch</div>
|
||||
@@ -110,14 +110,14 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
<Show when={apt()?.facts.length}>
|
||||
<div
|
||||
data-testid="resource-apt-action-facts"
|
||||
class="grid gap-1 rounded border border-border bg-surface px-2 py-1.5 sm:grid-cols-2"
|
||||
class="grid gap-1 rounded-sm border border-border bg-surface px-2 py-1.5 sm:grid-cols-2"
|
||||
>
|
||||
<For each={apt()?.facts ?? []}>
|
||||
{(fact) => (
|
||||
<InfoCardKeyValueRow
|
||||
label={fact.label}
|
||||
value={fact.value}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
@@ -127,7 +127,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(() => {
|
||||
const outcome = verificationOutcome()!;
|
||||
return (
|
||||
<div class={`rounded border px-2 py-1 text-[10px] ${outcome.className}`}>
|
||||
<div class={`rounded-sm border px-2 py-1 text-[10px] ${outcome.className}`}>
|
||||
<div class="font-medium">{outcome.label}</div>
|
||||
<div class="mt-0.5 opacity-80">{outcome.detail}</div>
|
||||
<Show when={outcome.evidenceSummary}>
|
||||
@@ -144,7 +144,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(recovery) => (
|
||||
<div
|
||||
data-testid="resource-action-recovery-truth"
|
||||
class="rounded border border-border bg-surface px-2 py-1 text-[10px] text-base-content"
|
||||
class="rounded-sm border border-border bg-surface px-2 py-1 text-[10px] text-base-content"
|
||||
>
|
||||
<div class="font-medium">
|
||||
Recovery: {formatActionCapabilityLabel(recovery().status)}
|
||||
@@ -162,7 +162,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
{(presentation) => (
|
||||
<div
|
||||
data-testid="resource-apt-action-next-step"
|
||||
class="rounded border border-blue-200 bg-blue-50 px-2 py-1 text-[10px] text-blue-900 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
class="rounded-sm border border-blue-200 bg-blue-50 px-2 py-1 text-[10px] text-blue-900/25 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
>
|
||||
<span class="font-medium">Next: </span>
|
||||
{presentation().nextStep}
|
||||
@@ -176,7 +176,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300'
|
||||
: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-300';
|
||||
return (
|
||||
<div class={`rounded border px-2 py-1 text-[10px] ${toneClass}`}>
|
||||
<div class={`rounded-sm border px-2 py-1 text-[10px] ${toneClass}`}>
|
||||
<div class="font-medium">
|
||||
{v.success
|
||||
? 'Legacy check passed (source unclassified)'
|
||||
@@ -186,7 +186,7 @@ const ActionHistoryRow: Component<{ audit: ActionAuditRecord }> = (props) => {
|
||||
<div class="mt-0.5 font-mono text-[10px] opacity-80">{v.command}</div>
|
||||
</Show>
|
||||
<Show when={v.output}>
|
||||
<div class="mt-0.5 whitespace-pre-wrap break-words">{v.output}</div>
|
||||
<div class="mt-0.5 whitespace-pre-wrap wrap-break-word">{v.output}</div>
|
||||
</Show>
|
||||
<Show when={v.note}>
|
||||
<div class="mt-0.5 italic">{v.note}</div>
|
||||
@@ -215,7 +215,7 @@ export const ResourceActionHistory: Component<ResourceActionHistoryProps> = (pro
|
||||
</div>
|
||||
|
||||
<Show when={props.error}>
|
||||
<div class="mt-2 rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="mt-2 rounded-sm border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<span>{props.error}</span>
|
||||
<button
|
||||
@@ -232,7 +232,7 @@ export const ResourceActionHistory: Component<ResourceActionHistoryProps> = (pro
|
||||
<Show
|
||||
when={props.audits.length > 0}
|
||||
fallback={
|
||||
<div class="mt-3 rounded border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
<div class="mt-3 rounded-sm border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
No actions yet.
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -92,36 +92,36 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div class="rounded bg-surface px-2 py-1">
|
||||
<div class="rounded-sm bg-surface px-2 py-1">
|
||||
<div class="flex flex-wrap items-center gap-1 text-[10px] text-base-content">
|
||||
{sourceHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={sourceHref}
|
||||
aria-label={`Open source resource ${sourceLabel} in Infrastructure`}
|
||||
>
|
||||
{sourceLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{sourceLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="text-muted">→</span>
|
||||
{targetHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={targetHref}
|
||||
aria-label={`Open target resource ${targetLabel} in Infrastructure`}
|
||||
>
|
||||
{targetLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{targetLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
{typeLabel}
|
||||
</span>
|
||||
</div>
|
||||
@@ -153,14 +153,14 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
const href = buildResourceHref(dependency);
|
||||
return href ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={href}
|
||||
aria-label={`Open dependency resource ${label} in Infrastructure`}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -180,14 +180,14 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
const href = buildResourceHref(dependent);
|
||||
return href ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={href}
|
||||
aria-label={`Open dependent resource ${label} in Infrastructure`}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -218,36 +218,36 @@ export const ResourceCorrelationSummary: Component<ResourceCorrelationSummaryPro
|
||||
})
|
||||
: '';
|
||||
return (
|
||||
<div class="rounded bg-surface px-2 py-1" title={headline}>
|
||||
<div class="rounded-sm bg-surface px-2 py-1" title={headline}>
|
||||
<div class="flex flex-wrap items-center gap-1 text-[10px] text-base-content">
|
||||
{sourceHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={sourceHref}
|
||||
aria-label={`Open source resource ${sourceLabel} in Infrastructure`}
|
||||
>
|
||||
{sourceLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{sourceLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="text-muted">→</span>
|
||||
{targetHref ? (
|
||||
<a
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-blue-700 hover:underline dark:text-blue-300"
|
||||
href={targetHref}
|
||||
aria-label={`Open target resource ${targetLabel} in Infrastructure`}
|
||||
>
|
||||
{targetLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{targetLabel}
|
||||
</span>
|
||||
)}
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[9px] text-muted">
|
||||
{patternLabel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ interface ResourceDetailDrawerProps {
|
||||
}
|
||||
|
||||
const TabAvailabilityNotice: Component<{ message: string }> = (props) => (
|
||||
<div class="rounded border border-dashed border-border bg-surface-hover p-4 text-sm text-muted">
|
||||
<div class="rounded-sm border border-dashed border-border bg-surface-hover p-4 text-sm text-muted">
|
||||
{props.message}
|
||||
</div>
|
||||
);
|
||||
@@ -84,7 +84,7 @@ const DrawerContent: Component<ResourceDetailDrawerProps> = (props) => {
|
||||
/>
|
||||
<h2
|
||||
id={headingId()}
|
||||
class="text-sm font-semibold text-base-content min-w-0 [overflow-wrap:anywhere] m-0"
|
||||
class="text-sm font-semibold text-base-content min-w-0 wrap-anywhere m-0"
|
||||
title={drawer.displayName()}
|
||||
>
|
||||
{drawer.displayName()}
|
||||
@@ -101,7 +101,7 @@ const DrawerContent: Component<ResourceDetailDrawerProps> = (props) => {
|
||||
<Show when={drawer.healthIssue()}>
|
||||
{(issue) => (
|
||||
<span
|
||||
class="inline-flex max-w-full items-center rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-700 dark:bg-amber-900 dark:text-amber-300"
|
||||
class="inline-flex max-w-full items-center rounded-sm bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-700 dark:bg-amber-900/25 dark:text-amber-300"
|
||||
title={issue().title}
|
||||
>
|
||||
{issue().compactLabel}
|
||||
|
||||
@@ -149,7 +149,7 @@ const HostDetailsDisclosure: Component<{
|
||||
class={props.class}
|
||||
contentClass={
|
||||
props.contentClass ??
|
||||
'mt-3 flex flex-wrap gap-3 [&>*]:flex-1 [&>*]:basis-[calc(50%-0.375rem)] [&>*]:min-w-[220px] [&>*]:max-w-full [&>*]:overflow-hidden'
|
||||
'mt-3 flex flex-wrap gap-3 *:flex-1 *:basis-[calc(50%-0.375rem)] *:min-w-[220px] *:max-w-full *:overflow-hidden'
|
||||
}
|
||||
dataTestId="resource-host-details-section"
|
||||
>
|
||||
@@ -270,7 +270,7 @@ export const ResourceAccessDisclosure: Component<{
|
||||
<a
|
||||
href={link.href}
|
||||
aria-label={link.ariaLabel}
|
||||
class="inline-flex items-center rounded border border-blue-200 bg-blue-50 px-2.5 py-1 text-[11px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200 dark:hover:bg-blue-900"
|
||||
class="inline-flex items-center rounded-sm border border-blue-200 bg-blue-50 px-2.5 py-1 text-[11px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
{link.compactLabel}
|
||||
</a>
|
||||
@@ -398,7 +398,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</TechnicalDetailsSection>
|
||||
|
||||
<Show when={resource.availability || (resource.availabilityChecks?.length ?? 0) > 0}>
|
||||
<div class="flex flex-wrap gap-3 [&>*]:min-w-[240px] [&>*]:flex-1">
|
||||
<div class="flex flex-wrap gap-3 *:min-w-[240px] *:flex-1">
|
||||
<AvailabilityProbeStatusCards
|
||||
availability={resource.availability}
|
||||
checks={resource.availabilityChecks}
|
||||
@@ -460,7 +460,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
label="Change kind"
|
||||
fieldBaseClass="space-y-1 text-[10px]"
|
||||
labelClass="text-muted"
|
||||
selectBaseClass="w-full rounded border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
selectBaseClass="w-full rounded-sm border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
value={drawer.timelineKindFilter()}
|
||||
onChange={(event) =>
|
||||
drawer.setTimelineKindFilter(
|
||||
@@ -476,7 +476,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
label="Source type"
|
||||
fieldBaseClass="space-y-1 text-[10px]"
|
||||
labelClass="text-muted"
|
||||
selectBaseClass="w-full rounded border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
selectBaseClass="w-full rounded-sm border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
value={drawer.timelineSourceTypeFilter()}
|
||||
onChange={(event) =>
|
||||
drawer.setTimelineSourceTypeFilter(
|
||||
@@ -492,7 +492,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
label="Source adapter"
|
||||
fieldBaseClass="space-y-1 text-[10px]"
|
||||
labelClass="text-muted"
|
||||
selectBaseClass="w-full rounded border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
selectBaseClass="w-full rounded-sm border border-border bg-base px-2 py-1 text-[11px] text-base-content"
|
||||
value={drawer.timelineSourceAdapterFilter()}
|
||||
onChange={(event) =>
|
||||
drawer.setTimelineSourceAdapterFilter(
|
||||
@@ -525,7 +525,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</Show>
|
||||
|
||||
<Show when={drawer.facetBundleError()}>
|
||||
<div class="mt-2 rounded border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="mt-2 rounded-sm border border-amber-200 bg-amber-50 px-2 py-1.5 text-[10px] text-amber-700 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<span>{drawer.facetBundleError()}</span>
|
||||
<button
|
||||
@@ -542,7 +542,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show
|
||||
when={drawer.sortedResourceTimeline().length > 0}
|
||||
fallback={
|
||||
<div class="mt-3 rounded border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
<div class="mt-3 rounded-sm border border-dashed border-border bg-surface-hover px-2 py-2 text-[10px] text-muted">
|
||||
No events yet.
|
||||
</div>
|
||||
}
|
||||
@@ -559,7 +559,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div class="rounded border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="rounded-sm border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-medium text-base-content">
|
||||
@@ -588,28 +588,28 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<InfoCardKeyValueRow
|
||||
label="Actor"
|
||||
value={change.actor}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={change.from || change.to}>
|
||||
<InfoCardKeyValueRow
|
||||
label="Transition"
|
||||
value={`${change.from || '—'} → ${change.to || '—'}`}
|
||||
valueClass="break-words"
|
||||
valueClass="wrap-break-word"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={change.reason}>
|
||||
<div class="mt-1 rounded border border-border bg-base px-2 py-1 text-[10px] text-base-content">
|
||||
<div class="mt-1 rounded-sm border border-border bg-base px-2 py-1 text-[10px] text-base-content">
|
||||
{change.reason}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={hasMetadataEntries(change.metadata)}>
|
||||
<details class="mt-1 rounded border border-border bg-base px-2 py-1">
|
||||
<details class="mt-1 rounded-sm border border-border bg-base px-2 py-1">
|
||||
<summary class="cursor-pointer list-none text-[10px] font-medium text-muted">
|
||||
Metadata
|
||||
</summary>
|
||||
<pre class="mt-2 overflow-auto whitespace-pre-wrap break-words text-[10px] text-base-content">
|
||||
<pre class="mt-2 overflow-auto whitespace-pre-wrap wrap-break-word text-[10px] text-base-content">
|
||||
{JSON.stringify(change.metadata ?? {}, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
@@ -621,7 +621,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
{(relatedResource) => {
|
||||
const label = drawer.resolveResourceLabel(relatedResource);
|
||||
return (
|
||||
<span class="inline-flex rounded bg-surface px-1.5 py-0.5 text-[10px] text-base-content">
|
||||
<span class="inline-flex rounded-sm bg-surface px-1.5 py-0.5 text-[10px] text-base-content">
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -668,7 +668,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
>
|
||||
<div
|
||||
data-testid="resource-support-sections"
|
||||
class="flex flex-wrap gap-3 [&>*]:flex-1 [&>*]:basis-[calc(50%-0.375rem)] [&>*]:min-w-[260px] [&>*]:max-w-full [&>*]:overflow-hidden"
|
||||
class="flex flex-wrap gap-3 *:flex-1 *:basis-[calc(50%-0.375rem)] *:min-w-[260px] *:max-w-full *:overflow-hidden"
|
||||
>
|
||||
<Show when={drawer.hasTrueNASDetails()}>
|
||||
<TrueNASDetailsDisclosure drawer={drawer} class="h-full" />
|
||||
@@ -707,7 +707,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
dataTestId="resource-service-details-section"
|
||||
>
|
||||
<Show when={resource.type === 'docker-host'}>
|
||||
<div class="rounded border border-sky-200 bg-sky-50 p-3 dark:border-sky-700 dark:bg-sky-900">
|
||||
<div class="rounded-sm border border-sky-200 bg-sky-50 p-3 dark:border-sky-700 dark:bg-sky-900/25">
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-sky-700 dark:text-sky-300">
|
||||
Docker runtime
|
||||
@@ -751,7 +751,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
drawer.dockerHostCommand()?.status
|
||||
}
|
||||
>
|
||||
<div class="rounded border border-sky-200 bg-surface px-2 py-1.5 text-[10px] dark:border-sky-700">
|
||||
<div class="rounded-sm border border-sky-200 bg-surface px-2 py-1.5 text-[10px] dark:border-sky-700">
|
||||
<InfoCardKeyValueRow
|
||||
label="Action"
|
||||
value={formatIdentifierLabel(drawer.dockerHostCommand()?.type, {
|
||||
@@ -790,12 +790,12 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</Show>
|
||||
|
||||
<Show when={drawer.dockerActionError()}>
|
||||
<div class="rounded border border-red-200 bg-red-50 px-2 py-1.5 text-[10px] text-red-700 dark:border-red-700 dark:bg-red-900 dark:text-red-200">
|
||||
<div class="rounded-sm border border-red-200 bg-red-50 px-2 py-1.5 text-[10px] text-red-700 dark:border-red-700 dark:bg-red-900/25 dark:text-red-200">
|
||||
{drawer.dockerActionError()}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={drawer.dockerActionNote()}>
|
||||
<div class="rounded border border-sky-200 bg-surface px-2 py-1.5 text-[10px] text-base-content dark:border-sky-700">
|
||||
<div class="rounded-sm border border-sky-200 bg-surface px-2 py-1.5 text-[10px] text-base-content dark:border-sky-700">
|
||||
{drawer.dockerActionNote()}
|
||||
</div>
|
||||
</Show>
|
||||
@@ -864,7 +864,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
pbs().connectionHealth,
|
||||
);
|
||||
return (
|
||||
<div class="rounded border border-indigo-200 bg-indigo-50 p-3 dark:border-indigo-700 dark:bg-indigo-900">
|
||||
<div class="rounded-sm border border-indigo-200 bg-indigo-50 p-3 dark:border-indigo-700 dark:bg-indigo-900/25">
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-indigo-700 dark:text-indigo-300">
|
||||
PBS
|
||||
@@ -903,20 +903,20 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={drawer.showPbsJobDetail()}>
|
||||
<div class="space-y-1.5 border-t border-indigo-200 pt-2 dark:border-indigo-700">
|
||||
<div class="grid grid-cols-2 gap-2 md:grid-cols-3">
|
||||
<div class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="text-[10px] text-muted">Datastores</div>
|
||||
<div class="text-sm font-semibold text-base-content">
|
||||
{formatInteger(pbs().datastoreCount)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<div class="text-[10px] text-muted">Jobs</div>
|
||||
<div class="text-sm font-semibold text-base-content">
|
||||
{formatInteger(drawer.pbsJobTotal())}
|
||||
</div>
|
||||
</div>
|
||||
<Show when={drawer.pbsActiveTaskCount() > 0}>
|
||||
<div class="rounded border border-emerald-200 bg-emerald-50 px-2 py-1.5 dark:border-emerald-700 dark:bg-emerald-950/40">
|
||||
<div class="rounded-sm border border-emerald-200 bg-emerald-50 px-2 py-1.5 dark:border-emerald-700 dark:bg-emerald-950/40">
|
||||
<div class="text-[10px] text-muted">Active</div>
|
||||
<div class="text-sm font-semibold text-emerald-700 dark:text-emerald-300">
|
||||
{formatInteger(drawer.pbsActiveTaskCount())}
|
||||
@@ -927,7 +927,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={drawer.pbsActiveTaskCount() > 0}>
|
||||
<div
|
||||
data-testid="pbs-active-tasks"
|
||||
class="rounded border border-emerald-200 bg-surface px-2 py-1.5 dark:border-emerald-700"
|
||||
class="rounded-sm border border-emerald-200 bg-surface px-2 py-1.5 dark:border-emerald-700"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-[10px] font-medium uppercase tracking-wide text-muted">
|
||||
@@ -977,7 +977,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={pbsJobHealthEvidence().evidenceCount > 0}>
|
||||
<div
|
||||
data-testid="pbs-job-health-evidence"
|
||||
class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700"
|
||||
class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700"
|
||||
>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-[10px] font-medium uppercase tracking-wide text-muted">
|
||||
@@ -990,7 +990,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<div class="mt-2 space-y-2 border-t border-indigo-200 pt-2 dark:border-indigo-700">
|
||||
<For each={pbsJobHealthEvidence().entries}>
|
||||
{(entry) => (
|
||||
<div class="min-w-0 rounded border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="min-w-0 rounded-sm border border-border bg-surface-hover px-2 py-1.5 text-[10px]">
|
||||
<div class="flex flex-wrap items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<div
|
||||
@@ -1071,7 +1071,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<details class="rounded border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<details class="rounded-sm border border-indigo-200 bg-surface px-2 py-1.5 dark:border-indigo-700">
|
||||
<summary class="flex cursor-pointer list-none items-center justify-between text-[10px] font-medium text-muted">
|
||||
<span>Types</span>
|
||||
<span class="text-muted">
|
||||
@@ -1113,7 +1113,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
pmg().connectionHealth,
|
||||
);
|
||||
return (
|
||||
<div class="rounded border border-rose-200 bg-rose-50 p-3 dark:border-rose-700 dark:bg-rose-900">
|
||||
<div class="rounded-sm border border-rose-200 bg-rose-50 p-3 dark:border-rose-700 dark:bg-rose-900/25">
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-rose-700 dark:text-rose-300">
|
||||
PMG
|
||||
@@ -1145,7 +1145,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={drawer.showPmgMailFlowDetail()}>
|
||||
<div class="space-y-1.5 border-t border-rose-200 pt-2 dark:border-rose-700">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="text-[10px] text-muted">Queue</div>
|
||||
<div
|
||||
class={`text-sm font-semibold ${drawer.pmgQueueBacklog() > 0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`}
|
||||
@@ -1153,7 +1153,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
{formatInteger(pmg().queueTotal)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<div class="text-[10px] text-muted">Backlog</div>
|
||||
<div
|
||||
class={`text-sm font-semibold ${drawer.pmgQueueBacklog() > 0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`}
|
||||
@@ -1165,7 +1165,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
<Show when={pmg().nodeCount || drawer.pmgUpdatedRelative()}>
|
||||
<div
|
||||
data-testid="pmg-support-context"
|
||||
class="space-y-1.5 rounded border border-dashed border-rose-200 bg-surface px-2 py-1.5 text-[10px] dark:border-rose-700"
|
||||
class="space-y-1.5 rounded-sm border border-dashed border-rose-200 bg-surface px-2 py-1.5 text-[10px] dark:border-rose-700"
|
||||
>
|
||||
<Show when={pmg().nodeCount}>
|
||||
<InfoCardKeyValueRow
|
||||
@@ -1186,7 +1186,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<details class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<details class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<summary class="cursor-pointer list-none text-[10px] font-medium text-muted">
|
||||
Queue detail
|
||||
</summary>
|
||||
@@ -1204,7 +1204,7 @@ export const ResourceDetailDrawerOverviewTab: Component<ResourceDetailDrawerOver
|
||||
</For>
|
||||
</div>
|
||||
</details>
|
||||
<details class="rounded border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<details class="rounded-sm border border-rose-200 bg-surface px-2 py-1.5 dark:border-rose-700">
|
||||
<summary class="cursor-pointer list-none text-[10px] font-medium text-muted">
|
||||
Mail detail
|
||||
</summary>
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export const ResourceDetailDrawerSupportDisclosure: Component<
|
||||
return (
|
||||
<div
|
||||
data-testid={props.dataTestId}
|
||||
class={`rounded border border-border bg-surface px-2 py-1.5 ${props.class ?? ''}`}
|
||||
class={`rounded-sm border border-border bg-surface px-2 py-1.5 ${props.class ?? ''}`}
|
||||
>
|
||||
<div class="flex min-w-0 items-center justify-between gap-2">
|
||||
<div class="flex min-w-0 flex-1 items-baseline gap-2">
|
||||
|
||||
@@ -171,7 +171,7 @@ const dockerSection = (docker: NonNullable<Resource['docker']>): DetailSection =
|
||||
<For each={labelEntries}>
|
||||
{([key, value]) => (
|
||||
<span
|
||||
class="inline-flex max-w-full items-center truncate rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
class="inline-flex max-w-full items-center truncate rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
title={value ? `${key}: ${value}` : key}
|
||||
>
|
||||
{key}
|
||||
@@ -202,7 +202,7 @@ export const InlineResourceSummaryTables: Component<ResourceSummaryPresentationP
|
||||
<For each={props.drawer.identityIpValues()}>
|
||||
{(ip) => (
|
||||
<span
|
||||
class="inline-flex items-center rounded bg-blue-100 px-1.5 py-0.5 text-[10px] text-blue-700 dark:bg-blue-900 dark:text-blue-200"
|
||||
class="inline-flex items-center rounded-sm bg-blue-100 px-1.5 py-0.5 text-[10px] text-blue-700 dark:bg-blue-900/25 dark:text-blue-200"
|
||||
title={ip}
|
||||
>
|
||||
{ip}
|
||||
@@ -227,7 +227,7 @@ export const InlineResourceSummaryTables: Component<ResourceSummaryPresentationP
|
||||
<For each={props.drawer.aliasPreviewValues()}>
|
||||
{(value) => (
|
||||
<span
|
||||
class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]"
|
||||
title={value}
|
||||
>
|
||||
{value}
|
||||
@@ -235,7 +235,7 @@ export const InlineResourceSummaryTables: Component<ResourceSummaryPresentationP
|
||||
)}
|
||||
</For>
|
||||
<Show when={props.drawer.hasAliasOverflow()}>
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px] text-muted">
|
||||
+
|
||||
{props.drawer.identityAliasValues().length -
|
||||
props.drawer.aliasPreviewValues().length}
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface ResourceFacetSummaryProps {
|
||||
}
|
||||
|
||||
const badgeBase =
|
||||
'inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium whitespace-nowrap';
|
||||
'inline-flex items-center rounded-sm px-1.5 py-0.5 text-[10px] font-medium whitespace-nowrap';
|
||||
|
||||
const countLabel = (count: number, singular: string, plural = `${singular}s`) =>
|
||||
`${count} ${count === 1 ? singular : plural}`;
|
||||
@@ -42,7 +42,7 @@ const buildFacetBadges = (
|
||||
badges.push({
|
||||
label: `Timeline ${changeCount}`,
|
||||
title: countLabel(changeCount, 'timeline event'),
|
||||
className: `${badgeBase} bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300`,
|
||||
className: `${badgeBase} bg-amber-100 text-amber-700 dark:bg-amber-900/25 dark:text-amber-300`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ const redactionLabelsRow = (labels: string[]): DetailRow | null => {
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<For each={labels}>
|
||||
{(label) => (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -575,7 +575,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</header>
|
||||
|
||||
<Show when={activeMaintenanceWindow()}>
|
||||
<div class="rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-200">
|
||||
<div class="rounded-sm border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<span class="font-semibold">Maintenance window active.</span> Attention on this resource
|
||||
<Show when={activeMaintenanceWindow()!.maintenanceScope === 'resource_and_descendants'}>
|
||||
{' '}
|
||||
@@ -594,7 +594,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</Show>
|
||||
|
||||
<Show when={scheduledMaintenanceWindow() && !activeMaintenanceWindow()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-200">
|
||||
<span class="font-semibold">Maintenance window scheduled.</span> Attention will be paused
|
||||
from {new Date(scheduledMaintenanceWindow()!.maintenanceStartAt!).toLocaleString()} until{' '}
|
||||
{new Date(scheduledMaintenanceWindow()!.maintenanceEndAt!).toLocaleString()}.
|
||||
@@ -607,7 +607,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</Show>
|
||||
|
||||
<Show when={persisted()?.maintenanceRecurrence && !activeMaintenanceWindow()}>
|
||||
<div class="rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
<div class="rounded-sm border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800 dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-200">
|
||||
<span class="font-semibold">Recurring maintenance configured.</span>{' '}
|
||||
{persisted()!
|
||||
.maintenanceRecurrence!.weekdays.map((weekday) => weekday.slice(0, 3))
|
||||
@@ -672,7 +672,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleOpenScheduler}
|
||||
disabled={saving()}
|
||||
class="min-h-11 self-start rounded border border-border px-2.5 py-1 text-xs font-medium text-base-content hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 self-start rounded-sm border border-border px-2.5 py-1 text-xs font-medium text-base-content hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Schedule window
|
||||
</button>
|
||||
@@ -683,7 +683,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleOpenScheduler}
|
||||
disabled={saving()}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-base-content border border-border rounded-sm hover:bg-surface-hover disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Edit window
|
||||
</button>
|
||||
@@ -691,7 +691,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleClearMaintenanceWindow}
|
||||
disabled={saving()}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-amber-700 border border-amber-200 rounded hover:bg-amber-50 dark:text-amber-300 dark:border-amber-800 dark:hover:bg-amber-900 disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-amber-700 border border-amber-200 rounded-sm hover:bg-amber-50 dark:text-amber-300 dark:border-amber-800 dark:hover:bg-amber-900/25 disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Cancel window
|
||||
</button>
|
||||
@@ -701,20 +701,20 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</Show>
|
||||
|
||||
<Show when={schedulerOpen()}>
|
||||
<div class="rounded border border-border bg-surface-alt/40 px-3 py-3 space-y-2">
|
||||
<div class="rounded-sm border border-border px-3 py-3 space-y-2">
|
||||
<div class="text-xs font-semibold text-base-content">Schedule maintenance window</div>
|
||||
<div class="inline-flex gap-0.5 rounded border border-border bg-surface p-0.5 text-xs">
|
||||
<div class="inline-flex gap-0.5 rounded-sm border border-border bg-surface p-0.5 text-xs">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setScheduleKind('once')}
|
||||
class={`rounded px-2.5 py-1 ${scheduleKind() === 'once' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
class={`rounded-sm px-2.5 py-1 ${scheduleKind() === 'once' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
One time
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setScheduleKind('recurring')}
|
||||
class={`rounded px-2.5 py-1 ${scheduleKind() === 'recurring' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
class={`rounded-sm px-2.5 py-1 ${scheduleKind() === 'recurring' ? 'bg-blue-600 text-white' : 'text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
Recurring
|
||||
</button>
|
||||
@@ -728,7 +728,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="datetime-local"
|
||||
value={scheduleStart()}
|
||||
onInput={(e) => setScheduleStart(e.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded-sm border border-border bg-surface px-2 py-1 text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
/>
|
||||
</label>
|
||||
@@ -738,7 +738,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="datetime-local"
|
||||
value={scheduleEnd()}
|
||||
onInput={(e) => setScheduleEnd(e.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded-sm border border-border bg-surface px-2 py-1 text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
/>
|
||||
</label>
|
||||
@@ -751,7 +751,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => applyPresetDuration(hours)}
|
||||
disabled={saving()}
|
||||
class="min-h-11 min-w-11 px-1.5 py-0.5 rounded border border-border hover:bg-surface-hover disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||
class="min-h-11 min-w-11 px-1.5 py-0.5 rounded-sm border border-border hover:bg-surface-hover disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||
>
|
||||
{hours}h
|
||||
</button>
|
||||
@@ -771,7 +771,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
aria-pressed={scheduleWeekdays().includes(value)}
|
||||
onClick={() => toggleMaintenanceWeekday(value)}
|
||||
class={`min-h-9 rounded border px-2 text-xs ${scheduleWeekdays().includes(value) ? 'border-blue-600 bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-200' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
class={`min-h-9 rounded-sm border px-2 text-xs ${scheduleWeekdays().includes(value) ? 'border-blue-600 bg-blue-50 text-blue-700 dark:bg-blue-950/25 dark:text-blue-200' : 'border-border text-muted hover:bg-surface-hover'}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
@@ -786,7 +786,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="time"
|
||||
value={scheduleRecurringStart()}
|
||||
onInput={(event) => setScheduleRecurringStart(event.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full rounded border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full rounded-sm border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
@@ -795,7 +795,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="time"
|
||||
value={scheduleRecurringEnd()}
|
||||
onInput={(event) => setScheduleRecurringEnd(event.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full rounded border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full rounded-sm border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
@@ -804,7 +804,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="text"
|
||||
value={scheduleTimezone()}
|
||||
onInput={(event) => setScheduleTimezone(event.currentTarget.value)}
|
||||
class="mt-0.5 min-h-11 w-full rounded border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full rounded-sm border border-border bg-surface px-2 text-xs text-base-content sm:min-h-0"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -835,7 +835,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={scheduleReason()}
|
||||
onInput={(e) => setScheduleReason(e.currentTarget.value)}
|
||||
placeholder="e.g. Q3 storage upgrade, kernel patch reboot"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded border border-border bg-surface px-2 py-1 text-base-content focus:outline-none focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
class="mt-0.5 min-h-11 w-full text-xs rounded-sm border border-border bg-surface px-2 py-1 text-base-content focus:outline-hidden focus:ring-1 focus:ring-blue-400 sm:min-h-0"
|
||||
disabled={saving()}
|
||||
maxLength={200}
|
||||
/>
|
||||
@@ -850,7 +850,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={() => setSchedulerOpen(false)}
|
||||
disabled={saving()}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors disabled:opacity-50 sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded-sm transition-colors disabled:opacity-50 sm:min-h-0"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -858,7 +858,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleScheduleSave}
|
||||
disabled={saving() || Boolean(scheduleValidationError())}
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded transition-colors sm:min-h-0"
|
||||
class="min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded-sm transition-colors sm:min-h-0"
|
||||
>
|
||||
{saving() ? 'Saving…' : 'Save window'}
|
||||
</button>
|
||||
@@ -886,7 +886,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</div>
|
||||
|
||||
<Show when={autoRemediationEnabled()}>
|
||||
<fieldset class="space-y-2 rounded border border-border bg-surface-alt/40 px-3 py-2.5">
|
||||
<fieldset class="space-y-2 rounded-sm border border-border px-3 py-2.5">
|
||||
<legend class="px-1 text-xs font-semibold text-base-content">
|
||||
Actions allowed by this limit
|
||||
</legend>
|
||||
@@ -945,7 +945,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={autoWindowStart()}
|
||||
onInput={(event) => setAutoWindowStart(event.currentTarget.value)}
|
||||
disabled={saving()}
|
||||
class="mt-0.5 w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
class="mt-0.5 w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-[11px] text-muted">
|
||||
@@ -955,7 +955,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={autoWindowEnd()}
|
||||
onInput={(event) => setAutoWindowEnd(event.currentTarget.value)}
|
||||
disabled={saving()}
|
||||
class="mt-0.5 w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
class="mt-0.5 w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-[11px] text-muted">
|
||||
@@ -965,7 +965,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
value={autoWindowTimezone()}
|
||||
onInput={(event) => setAutoWindowTimezone(event.currentTarget.value)}
|
||||
disabled={saving()}
|
||||
class="mt-0.5 w-full rounded border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
class="mt-0.5 w-full rounded-sm border border-border bg-surface px-2 py-1 text-xs text-base-content"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -1014,7 +1014,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</div>
|
||||
|
||||
<Show when={lifecycleState() === 'retired'}>
|
||||
<div class="rounded border border-border bg-surface-alt px-3 py-2 text-xs text-base-content">
|
||||
<div class="rounded-sm border border-border bg-surface-alt px-3 py-2 text-xs text-base-content">
|
||||
Retired resources remain in {inventoryOwnership().ownerLabel} inventory and keep their
|
||||
Pulse history. All alert attention and automated remediation are disabled until the
|
||||
lifecycle returns to Active.
|
||||
@@ -1041,7 +1041,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
</div>
|
||||
|
||||
<Show when={confirmingLock()}>
|
||||
<div class="rounded border border-red-300 bg-red-50 px-3 py-2.5 text-xs text-red-900 dark:border-red-800 dark:bg-red-950 dark:text-red-100">
|
||||
<div class="rounded-sm border border-red-300 bg-red-50 px-3 py-2.5 text-xs text-red-900/25 dark:border-red-800 dark:bg-red-950/25 dark:text-red-100">
|
||||
<p class="font-semibold">Lock this resource against all automated remediation?</p>
|
||||
<p class="mt-1 leading-relaxed">
|
||||
Pulse will refuse every dispatch targeting this resource, including approved actions
|
||||
@@ -1051,14 +1051,14 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
<button
|
||||
type="button"
|
||||
onClick={confirmLockToggle}
|
||||
class="rounded border border-red-400 bg-white px-2 py-1 text-xs font-medium text-red-900 hover:bg-red-100 dark:border-red-700 dark:bg-red-900 dark:text-red-100 dark:hover:bg-red-800"
|
||||
class="rounded-sm border border-red-400 bg-white px-2 py-1 text-xs font-medium text-red-900/25 hover:bg-red-100 dark:border-red-700 dark:bg-red-900/25 dark:text-red-100 dark:hover:bg-red-800"
|
||||
>
|
||||
Lock this resource
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cancelLockToggle}
|
||||
class="rounded border border-border bg-surface px-2 py-1 text-xs font-medium text-muted hover:bg-surface-hover"
|
||||
class="rounded-sm border border-border bg-surface px-2 py-1 text-xs font-medium text-muted hover:bg-surface-hover"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -1072,7 +1072,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:text-base-content hover:bg-surface-hover rounded transition-colors disabled:opacity-50"
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:text-base-content hover:bg-surface-hover rounded-sm transition-colors disabled:opacity-50"
|
||||
>
|
||||
Clear all overrides
|
||||
</button>
|
||||
@@ -1082,7 +1082,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleDiscard}
|
||||
disabled={saving()}
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded transition-colors disabled:opacity-50"
|
||||
class="px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-hover rounded-sm transition-colors disabled:opacity-50"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
@@ -1090,7 +1090,7 @@ export const ResourceOperatorStateSection: Component<ResourceOperatorStateSectio
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={saving() || Boolean(autoPolicyValidationError())}
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded transition-colors"
|
||||
class="px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 rounded-sm transition-colors"
|
||||
>
|
||||
{saving() ? 'Saving…' : 'Save overrides'}
|
||||
</button>
|
||||
|
||||
@@ -62,7 +62,7 @@ export const ResourcePolicySummary: Component<ResourcePolicySummaryProps> = (pro
|
||||
<div class="mt-2 flex flex-wrap gap-1">
|
||||
<For each={redactionSummaries()}>
|
||||
{(item) => (
|
||||
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
<span class="inline-flex items-center rounded-sm bg-surface-alt px-1.5 py-0.5 text-[10px]">
|
||||
{item.label} {item.count}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -350,7 +350,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
'cursor-pointer transition-all duration-200 relative group hover:bg-surface-hover';
|
||||
|
||||
if (isExpanded()) {
|
||||
return 'cursor-pointer transition-all duration-200 relative z-10 group bg-blue-50 dark:bg-blue-900';
|
||||
return 'cursor-pointer transition-all duration-200 relative z-10 group bg-blue-50 dark:bg-blue-900/25';
|
||||
}
|
||||
|
||||
let className = baseHover;
|
||||
@@ -433,7 +433,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
<Show when={healthIssue()}>
|
||||
{(issue) => (
|
||||
<span
|
||||
class="hidden shrink-0 whitespace-nowrap rounded bg-amber-100 px-1 text-[9px] font-medium text-amber-700 dark:bg-amber-900 dark:text-amber-300 lg:inline"
|
||||
class="hidden shrink-0 whitespace-nowrap rounded-sm bg-amber-100 px-1 text-[9px] font-medium text-amber-700 dark:bg-amber-900/25 dark:text-amber-300 lg:inline"
|
||||
title={issue().title}
|
||||
>
|
||||
{issue().compactLabel}
|
||||
@@ -443,7 +443,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
<Show when={availabilityProbe()}>
|
||||
{(probe) => (
|
||||
<span
|
||||
class={`hidden shrink-0 whitespace-nowrap rounded px-1 text-[9px] font-medium lg:inline ${probe().toneClassName}`}
|
||||
class={`hidden shrink-0 whitespace-nowrap rounded-sm px-1 text-[9px] font-medium lg:inline ${probe().toneClassName}`}
|
||||
title={probe().detailLabel}
|
||||
>
|
||||
{probe().methodLabel}
|
||||
@@ -470,7 +470,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
recentChanges={resource.recentChanges}
|
||||
counts={resource.facetCounts}
|
||||
maxVisibleBadges={1}
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap overflow-hidden lg:flex"
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap! overflow-hidden lg:flex"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -142,10 +142,10 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
||||
|
||||
const rowClass = createMemo(() => {
|
||||
const baseBorder = 'border-b border-border-subtle';
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-sm group ${baseBorder}`;
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-xs group ${baseBorder}`;
|
||||
|
||||
if (isExpanded()) {
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-sm z-10 group bg-blue-50 dark:bg-blue-900 ${baseBorder}`;
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-xs z-10 group bg-blue-50 dark:bg-blue-900/25 ${baseBorder}`;
|
||||
}
|
||||
|
||||
let className = baseHover;
|
||||
@@ -209,7 +209,7 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
||||
recentChanges={resource.recentChanges}
|
||||
counts={resource.facetCounts}
|
||||
maxVisibleBadges={1}
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap overflow-hidden lg:flex"
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap! overflow-hidden lg:flex"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -315,7 +315,7 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
||||
{(link) => (
|
||||
<a
|
||||
href={link().href}
|
||||
class="inline-flex items-center rounded border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
class="inline-flex items-center rounded-sm border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
title={link().label}
|
||||
aria-label={link().ariaLabel}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
|
||||
@@ -156,10 +156,10 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
||||
|
||||
const rowClass = createMemo(() => {
|
||||
const baseBorder = 'border-b border-border-subtle';
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-sm group ${baseBorder}`;
|
||||
const baseHover = `cursor-pointer transition-all duration-200 relative hover:shadow-xs group ${baseBorder}`;
|
||||
|
||||
if (isExpanded()) {
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-sm z-10 group bg-blue-50 dark:bg-blue-900 ${baseBorder}`;
|
||||
return `cursor-pointer transition-all duration-200 relative hover:shadow-xs z-10 group bg-blue-50 dark:bg-blue-900/25 ${baseBorder}`;
|
||||
}
|
||||
|
||||
let className = baseHover;
|
||||
@@ -223,7 +223,7 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
||||
recentChanges={resource.recentChanges}
|
||||
counts={resource.facetCounts}
|
||||
maxVisibleBadges={1}
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap overflow-hidden lg:flex"
|
||||
class="hidden max-w-[48%] shrink-0 flex-nowrap! overflow-hidden lg:flex"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -355,7 +355,7 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
||||
{(link) => (
|
||||
<a
|
||||
href={link().href}
|
||||
class="inline-flex items-center rounded border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
class="inline-flex items-center rounded-sm border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/25 dark:text-blue-200 dark:hover:bg-blue-800"
|
||||
title={link().label}
|
||||
aria-label={link().ariaLabel}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
|
||||
@@ -58,7 +58,7 @@ export const UnifiedResourceSourceBadgeCell: Component<UnifiedResourceSourceBadg
|
||||
</For>
|
||||
<Show when={hiddenBadgeCount() > 0}>
|
||||
<span
|
||||
class="inline-flex min-w-0 max-w-full items-center overflow-hidden rounded bg-surface-alt px-1 py-0.5 text-[10px] font-medium text-muted"
|
||||
class="inline-flex min-w-0 max-w-full items-center overflow-hidden rounded-sm bg-surface-alt px-1 py-0.5 text-[10px] font-medium text-muted"
|
||||
aria-label={`Additional systems: ${hiddenBadges()
|
||||
.map((badge) => badge.title ?? badge.label)
|
||||
.join(', ')}`}
|
||||
|
||||
+1
-1
@@ -610,7 +610,7 @@ describe('ResourceDetailDrawer change history section', () => {
|
||||
await within(contextSection).findByText('Analysis');
|
||||
expect(contextSection.querySelector('table')).toBeTruthy();
|
||||
expect(contextSection.querySelector('tbody')).toBeTruthy();
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-sm"]')).toHaveLength(1);
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-xs"]')).toHaveLength(1);
|
||||
expect(screen.getByText('Health')).toBeInTheDocument();
|
||||
expect(screen.getByText('A · 92/100')).toBeInTheDocument();
|
||||
expect(screen.getByText('Trend')).toBeInTheDocument();
|
||||
|
||||
+2
-2
@@ -348,7 +348,7 @@ describe('ResourceDetailDrawer runtime and identity cards', () => {
|
||||
expect(
|
||||
getByTestId('resource-host-details-section')
|
||||
.querySelector('.mt-3.flex.flex-wrap')
|
||||
?.classList.contains('[&>*]:min-w-[220px]'),
|
||||
?.classList.contains('*:min-w-[220px]'),
|
||||
).toBe(true);
|
||||
expect(getByText('Network')).toBeInTheDocument();
|
||||
expect(getByText('Disks')).toBeInTheDocument();
|
||||
@@ -977,7 +977,7 @@ describe('ResourceDetailDrawer runtime and identity cards', () => {
|
||||
const contextSection = getByTestId('resource-investigation-context');
|
||||
expect(contextSection.querySelector('table')).toBeTruthy();
|
||||
expect(contextSection.querySelector('tbody')).toBeTruthy();
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-sm"]')).toHaveLength(1);
|
||||
expect(contextSection.querySelectorAll('tbody[class*="shadow-xs"]')).toHaveLength(1);
|
||||
expect(getByText('Health')).toBeInTheDocument();
|
||||
expect(getByText('A · 92/100')).toBeInTheDocument();
|
||||
expect(getByText('Trend')).toBeInTheDocument();
|
||||
|
||||
+1
-1
@@ -250,7 +250,7 @@ describe('ResourceDetailDrawer TrueNAS details', () => {
|
||||
expect(getByTestId('resource-technical-summary-section').querySelector('table')).toBeTruthy();
|
||||
expect(() => getByTestId('resource-current-state-section')).toThrow();
|
||||
expect(getByTestId('resource-identity-section').tagName).toBe('TBODY');
|
||||
expect(summary.querySelector('[class*="shadow-sm"]')).not.toBeNull();
|
||||
expect(summary.querySelector('[class*="shadow-xs"]')).not.toBeNull();
|
||||
expect(getByRole('button', { name: 'Hide TrueNAS' })).toBeInTheDocument();
|
||||
const trueNASSection = getByTestId('resource-truenas-details-section');
|
||||
expect(
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ describe('ResourceOperatorStateSection', () => {
|
||||
expect(sectionSource).toContain(
|
||||
'flex flex-col items-stretch justify-between gap-3 border-t border-border-subtle pt-2 sm:flex-row sm:items-center',
|
||||
);
|
||||
expect(sectionSource).toContain('min-h-11 self-start rounded border border-border');
|
||||
expect(sectionSource).toContain('min-h-11 self-start rounded-sm border border-border');
|
||||
expect(sectionSource).toContain(
|
||||
'min-h-11 px-2.5 py-1 text-xs font-medium text-white bg-blue-600',
|
||||
);
|
||||
|
||||
+1
-1
@@ -823,7 +823,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
||||
expect(source).toContain('createSummaryInteractiveRowPreviewHandlers');
|
||||
expect(source).toContain('SummaryRowActionButton');
|
||||
expect(source).toContain('hideWhenRowTappableOnMobile');
|
||||
expect(source).not.toContain('bg-blue-50 dark:bg-blue-900 ring-1 ring-blue-300');
|
||||
expect(source).not.toContain('bg-blue-50 dark:bg-blue-900/25 ring-1 ring-blue-300');
|
||||
expect(source).not.toContain('bg-blue-100 dark:bg-blue-800');
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -16,9 +16,9 @@ import type { Resource } from '@/types/resource';
|
||||
// Mirrors the private badge-class constants in resourceDetailDrawerOperationalModel.ts
|
||||
// so the assertions here stay brittle to drift in those exact Tailwind tokens.
|
||||
const SUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400';
|
||||
const UNSUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
|
||||
type SourceStatusMap = NonNullable<PlatformData['sourceStatus']>;
|
||||
|
||||
|
||||
+5
-5
@@ -40,31 +40,31 @@ describe('resourceDetailDrawerOperationalModel', () => {
|
||||
{
|
||||
label: 'K8s Node CPU/Memory',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Node CPU and memory metrics are available.',
|
||||
},
|
||||
{
|
||||
label: 'Node Telemetry (Agent)',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Linked Pulse agent provides node uptime, temperature, disk, network, and disk I/O.',
|
||||
},
|
||||
{
|
||||
label: 'Pod CPU/Memory',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Pod CPU and memory metrics are available.',
|
||||
},
|
||||
{
|
||||
label: 'Pod Ephemeral Disk',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400',
|
||||
title: 'Pod ephemeral storage usage is available.',
|
||||
},
|
||||
{
|
||||
label: 'Pod Disk I/O Unsupported',
|
||||
classes:
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted',
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted',
|
||||
title:
|
||||
'Pod disk read/write throughput is not collected by the Kubernetes integration path today.',
|
||||
},
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ describe('unifiedResourceTableStateModel', () => {
|
||||
expect(getUnifiedResourceTableShellClass('mobile')).not.toContain('min-w-[640px]');
|
||||
expect(getUnifiedResourceTableShellClass('compact')).toContain('min-w-[640px]');
|
||||
expect(getUnifiedResourceTableShellClass('wide')).toContain('min-w-[640px]');
|
||||
expect(getUnifiedResourceTableShellClass('wide')).not.toContain('min-w-[max-content]');
|
||||
expect(getUnifiedResourceTableShellClass('wide')).not.toContain('min-w-max');
|
||||
// Mobile keeps the prioritized column set but preserves a readable width
|
||||
// floor inside the shared horizontal-scroll shell. Wider modes keep all
|
||||
// host columns visible while compressing their tracks before any
|
||||
|
||||
+2
-2
@@ -19,9 +19,9 @@ export type ResourceDetailDrawerOperationalLink = ServiceDetailLink;
|
||||
type SourceStatusMap = NonNullable<PlatformData['sourceStatus']>;
|
||||
|
||||
const SUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-cyan-100 text-cyan-700 dark:bg-cyan-900/25 dark:text-cyan-400';
|
||||
const UNSUPPORTED_BADGE_CLASS =
|
||||
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
'inline-flex items-center rounded-sm px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted';
|
||||
|
||||
export const buildKubernetesCapabilityBadges = (
|
||||
capabilities?: KubernetesMetricCapabilities,
|
||||
|
||||
@@ -186,12 +186,12 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="w-[12rem]">
|
||||
<div class="w-48">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder={drawerPresentation.searchPlaceholder}
|
||||
inputClass="py-1 text-xs font-medium shadow-sm"
|
||||
inputClass="py-1 text-xs font-medium shadow-xs"
|
||||
typeToSearch
|
||||
clearOnEscape
|
||||
/>
|
||||
@@ -204,7 +204,7 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
fieldBaseClass={filterGroupClass}
|
||||
labelClass={filterLabelClass}
|
||||
selectBaseClass={filterSelectClass}
|
||||
selectClass="min-w-[10rem]"
|
||||
selectClass="min-w-40"
|
||||
value={namespace()}
|
||||
aria-label={drawerPresentation.namespaceFilterLabel}
|
||||
onChange={(e) => setNamespace(e.currentTarget.value)}
|
||||
@@ -217,7 +217,7 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openPods(namespace() || undefined)}
|
||||
class="rounded-md border border-border px-3 py-1 text-xs font-semibold shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border px-3 py-1 text-xs font-semibold shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
{drawerPresentation.openPodsLabel}
|
||||
</button>
|
||||
@@ -330,7 +330,7 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
type="button"
|
||||
aria-label={drawerPresentation.viewPodsLabel}
|
||||
onClick={() => openPods(dep.kubernetes?.namespace)}
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
<PlatformResponsiveTableLabel
|
||||
compact="Pods"
|
||||
|
||||
@@ -95,12 +95,12 @@ export const K8sNamespacesDrawer: Component<{
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="w-[12rem]">
|
||||
<div class="w-48">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder={drawerPresentation.searchPlaceholder}
|
||||
inputClass="py-1 text-xs font-medium shadow-sm"
|
||||
inputClass="py-1 text-xs font-medium shadow-xs"
|
||||
typeToSearch
|
||||
clearOnEscape
|
||||
/>
|
||||
@@ -108,7 +108,7 @@ export const K8sNamespacesDrawer: Component<{
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openPods(null)}
|
||||
class="rounded-md border border-border bg-surface px-3 py-1 text-xs font-semibold shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-3 py-1 text-xs font-semibold shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
{drawerPresentation.openAllPodsLabel}
|
||||
</button>
|
||||
@@ -222,7 +222,7 @@ export const K8sNamespacesDrawer: Component<{
|
||||
type="button"
|
||||
aria-label={drawerPresentation.openPodsLabel}
|
||||
onClick={() => openPods(row.namespace)}
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
<PlatformResponsiveTableLabel
|
||||
compact="Pods"
|
||||
@@ -234,7 +234,7 @@ export const K8sNamespacesDrawer: Component<{
|
||||
type="button"
|
||||
aria-label={drawerPresentation.viewDeploymentsLabel}
|
||||
onClick={() => props.onOpenDeployments?.(row.namespace)}
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
|
||||
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-xs hover:bg-surface-hover"
|
||||
>
|
||||
<PlatformResponsiveTableLabel
|
||||
compact="Deployments"
|
||||
|
||||
@@ -442,9 +442,9 @@ const LoginForm: Component<{
|
||||
<div class="max-w-md w-full space-y-8">
|
||||
{/* Demo Credentials Banner */}
|
||||
<Show when={demoModeEnabled}>
|
||||
<div class="bg-surface rounded-md p-4 shadow-sm border border-blue-200 dark:border-blue-800 animate-fade-in">
|
||||
<div class="bg-surface rounded-md p-4 shadow-xs border border-blue-200 dark:border-blue-800 animate-fade-in">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-shrink-0 w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center">
|
||||
<div class="shrink-0 w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center">
|
||||
<svg
|
||||
class="w-5 h-5 text-white"
|
||||
fill="none"
|
||||
@@ -466,11 +466,11 @@ const LoginForm: Component<{
|
||||
fallback={
|
||||
<div class="text-sm text-muted">
|
||||
Login with{' '}
|
||||
<code class="bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded font-mono text-xs">
|
||||
<code class="bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded-sm font-mono text-xs">
|
||||
demo
|
||||
</code>{' '}
|
||||
/{' '}
|
||||
<code class="bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded font-mono text-xs">
|
||||
<code class="bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded-sm font-mono text-xs">
|
||||
demo
|
||||
</code>
|
||||
</div>
|
||||
@@ -503,7 +503,7 @@ const LoginForm: Component<{
|
||||
</Show>
|
||||
</div>
|
||||
<form
|
||||
class="mt-8 space-y-6 bg-surface rounded-md p-8 shadow-sm border border-border animate-slide-up"
|
||||
class="mt-8 space-y-6 bg-surface rounded-md p-8 shadow-xs border border-border animate-slide-up"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{/* Multi-Provider SSO Section */}
|
||||
@@ -513,7 +513,7 @@ const LoginForm: Component<{
|
||||
{(provider) => (
|
||||
<button
|
||||
type="button"
|
||||
class={`w-full inline-flex items-center justify-center gap-2 px-4 py-3 rounded-md border border-blue-500 text-blue-600 hover:bg-blue-50 transition dark:border-blue-400 dark:text-blue-200 dark:hover:bg-blue-900 ${oidcLoading() ? 'opacity-75 cursor-wait' : ''}`}
|
||||
class={`w-full inline-flex items-center justify-center gap-2 px-4 py-3 rounded-md border border-blue-500 text-blue-600 hover:bg-blue-50 transition dark:border-blue-400 dark:text-blue-200 dark:hover:bg-blue-900/25 ${oidcLoading() ? 'opacity-75 cursor-wait' : ''}`}
|
||||
disabled={oidcLoading()}
|
||||
onClick={() => {
|
||||
window.location.href = provider.loginUrl;
|
||||
@@ -538,12 +538,12 @@ const LoginForm: Component<{
|
||||
)}
|
||||
</For>
|
||||
<Show when={oidcError()}>
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-800 px-3 py-2 text-sm text-red-600 dark:text-red-300">
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900/25 border border-red-200 dark:border-red-800 px-3 py-2 text-sm text-red-600 dark:text-red-300">
|
||||
{oidcError()}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={oidcMessage()}>
|
||||
<div class="rounded-md bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 px-3 py-2 text-sm text-green-600 dark:text-green-300">
|
||||
<div class="rounded-md bg-green-50 dark:bg-green-900/25 border border-green-200 dark:border-green-700 px-3 py-2 text-sm text-green-600 dark:text-green-300">
|
||||
{oidcMessage()}
|
||||
</div>
|
||||
</Show>
|
||||
@@ -581,7 +581,7 @@ const LoginForm: Component<{
|
||||
type="text"
|
||||
autocomplete="username"
|
||||
required
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
placeholder="Username"
|
||||
value={username()}
|
||||
onInput={(e) => setUsername(e.currentTarget.value)}
|
||||
@@ -607,7 +607,7 @@ const LoginForm: Component<{
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
class="appearance-none relative block w-full pl-10 pr-3 py-3 border border-border placeholder-gray-500 rounded-md focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all sm:text-sm"
|
||||
placeholder="Password"
|
||||
value={password()}
|
||||
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||
@@ -620,7 +620,7 @@ const LoginForm: Component<{
|
||||
type="checkbox"
|
||||
checked={rememberMe()}
|
||||
onChange={(e) => setRememberMe(e.currentTarget.checked)}
|
||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-border rounded cursor-pointer"
|
||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-border rounded-sm cursor-pointer"
|
||||
/>
|
||||
<label
|
||||
for="remember-me"
|
||||
@@ -638,11 +638,11 @@ const LoginForm: Component<{
|
||||
class={`rounded-md p-4 ${
|
||||
error().includes('locked')
|
||||
? 'bg-orange-50 dark:bg-orange-900'
|
||||
: 'bg-red-50 dark:bg-red-900'
|
||||
: 'bg-red-50 dark:bg-red-900/25'
|
||||
}`}
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="shrink-0">
|
||||
<Show
|
||||
when={error().includes('locked')}
|
||||
fallback={
|
||||
@@ -689,7 +689,7 @@ const LoginForm: Component<{
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading()}
|
||||
class="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
class="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<Show when={loading()}>
|
||||
<LoadingSpinner size="button" tone="inverse" class="-ml-1 mr-3" />
|
||||
@@ -703,7 +703,7 @@ const LoginForm: Component<{
|
||||
<div class="border-t border-border pt-4 text-center">
|
||||
<a
|
||||
href={TROUBLESHOOTING_DOC_URL}
|
||||
class="inline-flex min-h-10 items-center rounded px-1 text-xs font-medium text-blue-700 underline-offset-2 transition-colors hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60 dark:text-blue-300 sm:min-h-9"
|
||||
class="inline-flex min-h-10 items-center rounded-sm px-1 text-xs font-medium text-blue-700 underline-offset-2 transition-colors hover:underline focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60 dark:text-blue-300 sm:min-h-9"
|
||||
>
|
||||
Can’t sign in?
|
||||
</a>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function OrgSwitcher(props: OrgSwitcherProps) {
|
||||
label="Organization"
|
||||
labelClass="sr-only"
|
||||
fieldBaseClass="contents"
|
||||
selectBaseClass="h-7 max-w-44 rounded-md border border-border bg-surface px-2 text-xs text-base-content shadow-sm transition-colors focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
selectBaseClass="h-7 max-w-44 rounded-md border border-border bg-surface px-2 text-xs text-base-content shadow-xs transition-colors focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
aria-label="Organization"
|
||||
value={props.selectedOrgId}
|
||||
disabled={Boolean(props.loading)}
|
||||
|
||||
@@ -128,7 +128,7 @@ export const SecurityWarning: Component = () => {
|
||||
return (
|
||||
<Show when={shouldShow()}>
|
||||
<div
|
||||
class={`relative z-20 border-b shadow-sm ${warningPresentation().background} ${warningPresentation().border}`}
|
||||
class={`relative z-20 border-b shadow-xs ${warningPresentation().background} ${warningPresentation().border}`}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
@@ -149,7 +149,7 @@ export const SecurityWarning: Component = () => {
|
||||
</span>
|
||||
}
|
||||
size="sm"
|
||||
class="flex-1 min-w-[12rem]"
|
||||
class="flex-1 min-w-48"
|
||||
titleClass="text-base-content"
|
||||
/>
|
||||
<button
|
||||
@@ -246,7 +246,7 @@ export const SecurityWarning: Component = () => {
|
||||
>
|
||||
Dismiss ▼
|
||||
</button>
|
||||
<div class="absolute left-0 top-full mt-1 bg-surface rounded shadow-sm border border-border opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity">
|
||||
<div class="absolute left-0 top-full mt-1 bg-surface rounded-sm shadow-xs border border-border opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDismiss('day')}
|
||||
|
||||
@@ -92,7 +92,7 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
||||
fieldBaseClass="contents"
|
||||
value={state.selectedSessionId()}
|
||||
onChange={(e) => state.setSelectedSessionId(e.currentTarget.value)}
|
||||
selectBaseClass="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded"
|
||||
selectBaseClass="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded-sm"
|
||||
disabled={state.saving()}
|
||||
>
|
||||
<For each={state.chatSessions()}>
|
||||
@@ -116,7 +116,7 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
||||
type="button"
|
||||
onClick={state.handleSessionSummarize}
|
||||
disabled={!state.selectedSessionId() || state.sessionActionLoading() !== null}
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-3 py-2 text-sm font-medium rounded border border-border bg-surface text-base-content hover:bg-surface-hover disabled:opacity-50"
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-3 py-2 text-sm font-medium rounded-sm border border-border bg-surface text-base-content hover:bg-surface-hover disabled:opacity-50"
|
||||
>
|
||||
{state.sessionActionLoading() === 'summarize'
|
||||
? 'Summarizing...'
|
||||
|
||||
@@ -132,13 +132,13 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
const toneClasses = () => {
|
||||
switch (tone()) {
|
||||
case 'success':
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900 text-green-700 dark:text-green-300';
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/25 text-green-700 dark:text-green-300';
|
||||
case 'warning':
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-700 dark:text-amber-300';
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 text-amber-700 dark:text-amber-300';
|
||||
case 'neutral':
|
||||
return 'border-border bg-surface-alt text-base-content';
|
||||
case 'error':
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900 text-red-700 dark:text-red-300';
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25 text-red-700 dark:text-red-300';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -268,8 +268,8 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
disabled={state.saving()}
|
||||
class={`inline-flex min-h-9 items-center rounded-md px-3 py-1.5 text-sm disabled:opacity-50 whitespace-nowrap ${
|
||||
state.patrolModelReadinessRunning()
|
||||
? 'bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800'
|
||||
: 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800'
|
||||
? 'bg-amber-100 dark:bg-amber-900/25 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800'
|
||||
: 'bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800'
|
||||
}`}
|
||||
>
|
||||
{state.patrolModelReadinessRunning() ? 'Cancel check' : 'Check Patrol model'}
|
||||
@@ -282,7 +282,7 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
</Show>
|
||||
<Show when={result()}>
|
||||
{(r) => (
|
||||
<div class={`rounded border px-3 py-2 ${toneClasses()}`}>
|
||||
<div class={`rounded-sm border px-3 py-2 ${toneClasses()}`}>
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<p class="text-xs font-medium">{headline()}</p>
|
||||
<Show when={r().duration_ms > 0}>
|
||||
@@ -295,7 +295,7 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
<div class="mt-2 grid gap-1 sm:grid-cols-2">
|
||||
<For each={dimensionRows()}>
|
||||
{(dimension) => (
|
||||
<div class="rounded bg-white/40 px-2 py-1 dark:bg-black/10">
|
||||
<div class="rounded-sm bg-white/40 px-2 py-1 dark:bg-black/10">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-[11px]">{dimension.label}</span>
|
||||
<span
|
||||
@@ -312,7 +312,7 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div class="mt-2 border-t border-current/15 pt-2">
|
||||
<div class="mt-2 border-t pt-2">
|
||||
<p class="text-[11px] font-medium mb-1">Autonomy suitability</p>
|
||||
<div class="grid gap-1 sm:grid-cols-2">
|
||||
<For each={modeRows()}>
|
||||
@@ -378,11 +378,11 @@ export const PatrolModelReadinessControl: Component<{ state: AISettingsState }>
|
||||
const patrolCostToneClasses = (tone: PatrolCostTone) => {
|
||||
switch (tone) {
|
||||
case 'positive':
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900 text-green-800 dark:text-green-200';
|
||||
return 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/25 text-green-800 dark:text-green-200';
|
||||
case 'warning':
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200';
|
||||
return 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 text-amber-800 dark:text-amber-200';
|
||||
case 'danger':
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900 text-red-800 dark:text-red-200';
|
||||
return 'border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/25 text-red-800 dark:text-red-200';
|
||||
default:
|
||||
return 'border-border bg-surface-alt text-base-content';
|
||||
}
|
||||
@@ -411,7 +411,7 @@ export const PatrolCostPreview: Component<{ state: AISettingsState }> = (props)
|
||||
<Show when={presentation()}>
|
||||
{(view) => (
|
||||
<div
|
||||
class={`mt-2 rounded border px-3 py-2 ${patrolCostToneClasses(view().tone)}`}
|
||||
class={`mt-2 rounded-sm border px-3 py-2 ${patrolCostToneClasses(view().tone)}`}
|
||||
data-testid="patrol-cost-preview"
|
||||
role="status"
|
||||
>
|
||||
@@ -583,12 +583,7 @@ export const AIModelOverrideField: Component<{
|
||||
<SelectedModelGuidanceNote annotation={selectedAnnotation} />
|
||||
<Show when={selectedModel() && !isModelProviderConfigured(selectedModel(), state.settings())}>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1 flex items-center gap-1">
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -701,12 +696,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
||||
<SelectedModelGuidanceNote annotation={sharedSelectedAnnotation} />
|
||||
<Show when={state.modelsError()}>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1 flex items-center gap-1">
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -722,12 +712,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
||||
when={state.form.model && !isModelProviderConfigured(state.form.model, state.settings())}
|
||||
>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1 flex items-center gap-1">
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
||||
@@ -78,7 +78,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
type="button"
|
||||
onClick={() => void props.runProviderPreflight()}
|
||||
disabled={props.preflightRunning() || props.saving()}
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
>
|
||||
{props.preflightRunning() ? 'Checking...' : 'Run Preflight'}
|
||||
</button>
|
||||
@@ -163,13 +163,13 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm">{config.title}</span>
|
||||
<Show when={configured()}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold bg-green-100 dark:bg-green-900/25 text-green-700 dark:text-green-300 rounded-sm">
|
||||
{config.configuredLabel}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={configured()}>
|
||||
<span
|
||||
class={`px-1.5 py-0.5 text-[10px] font-semibold rounded ${getAIProviderHealthPresentation(health().status).badgeClass}`}
|
||||
class={`px-1.5 py-0.5 text-[10px] font-semibold rounded-sm ${getAIProviderHealthPresentation(health().status).badgeClass}`}
|
||||
>
|
||||
{getAIProviderHealthPresentation(health().status).label}
|
||||
</span>
|
||||
@@ -235,7 +235,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
}
|
||||
disabled={props.saving()}
|
||||
aria-label={`Enable ${getAIProviderDisplayName(config.provider)}`}
|
||||
class="mt-0.5 h-4 w-4 rounded border-border"
|
||||
class="mt-0.5 h-4 w-4 rounded-sm border-border"
|
||||
/>
|
||||
<span>
|
||||
Use this machine's existing subscription login. Pulse stores no OAuth
|
||||
@@ -324,7 +324,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
type="button"
|
||||
onClick={() => void props.handleTestProvider(config.provider)}
|
||||
disabled={props.testingProvider() === config.provider || props.saving()}
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900/25 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800 disabled:opacity-50"
|
||||
>
|
||||
{props.testingProvider() === config.provider ? 'Testing...' : 'Test'}
|
||||
</button>
|
||||
@@ -332,7 +332,7 @@ export const AIProviderConfigurationSection: Component<AIProviderConfigurationSe
|
||||
type="button"
|
||||
onClick={() => void props.handleClearProvider(config.provider)}
|
||||
disabled={props.saving()}
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-800 disabled:opacity-50"
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center rounded-md px-3 py-2 text-sm bg-red-100 dark:bg-red-900/25 text-red-700 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-800 disabled:opacity-50"
|
||||
title={config.clearTitle}
|
||||
>
|
||||
Remove
|
||||
|
||||
@@ -42,7 +42,7 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
<div class="rounded-md border border-blue-200 dark:border-blue-800 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full min-h-10 sm:min-h-9 px-3 py-2.5 flex items-center justify-between bg-blue-50 dark:bg-blue-900 hover:bg-blue-100 dark:hover:bg-blue-900 transition-colors text-left"
|
||||
class="w-full min-h-10 sm:min-h-9 px-3 py-2.5 flex items-center justify-between bg-blue-50 dark:bg-blue-900/25 hover:bg-blue-100 dark:hover:bg-blue-900/25 transition-colors text-left"
|
||||
onClick={() => state.setShowDiscoverySettings(!state.showDiscoverySettings())}
|
||||
aria-expanded={state.showDiscoverySettings()}
|
||||
aria-controls="ai-discovery-controls-panel"
|
||||
@@ -63,14 +63,14 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-base-content">Service Context</span>
|
||||
<Show when={state.form.discoveryEnabled}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-300 rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-300 rounded-sm">
|
||||
{state.form.discoveryIntervalHours > 0
|
||||
? `Auto ${state.form.discoveryIntervalHours}h`
|
||||
: 'Manual only'}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!state.form.discoveryEnabled}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-surface-hover text-muted rounded">
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-medium bg-surface-hover text-muted rounded-sm">
|
||||
Off
|
||||
</span>
|
||||
</Show>
|
||||
@@ -119,8 +119,8 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
disabled={state.saving()}
|
||||
fieldBaseClass="flex"
|
||||
fieldClass="items-center gap-3"
|
||||
labelClass="text-xs font-medium text-muted w-32 flex-shrink-0"
|
||||
selectBaseClass="flex-1 px-2 py-1 text-sm border border-border rounded bg-surface"
|
||||
labelClass="text-xs font-medium text-muted w-32 shrink-0"
|
||||
selectBaseClass="flex-1 px-2 py-1 text-sm border border-border rounded-sm bg-surface"
|
||||
>
|
||||
<option value="0">Manual only</option>
|
||||
<option value="6">Every 6 hours</option>
|
||||
@@ -137,7 +137,7 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="flex flex-col gap-2 rounded border border-border bg-surface-alt px-3 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
||||
<div class="flex flex-col gap-2 rounded-sm border border-border bg-surface-alt px-3 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
||||
<p class="text-[10px] text-muted sm:flex-1">
|
||||
{state.form.discoveryEnabled && state.form.discoveryIntervalHours > 0
|
||||
? 'Runs the same scan used by the schedule.'
|
||||
@@ -147,7 +147,7 @@ export const AIDiscoveryControlsSection: Component<AIRuntimeControlsSectionProps
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex min-h-10 items-center gap-1.5 self-start rounded-md border border-border px-3 py-1.5 text-xs font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9 sm:self-auto sm:flex-shrink-0"
|
||||
class="inline-flex min-h-10 items-center gap-1.5 self-start rounded-md border border-border px-3 py-1.5 text-xs font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9 sm:self-auto sm:shrink-0"
|
||||
onClick={() => void state.handleRunDiscoveryRefresh()}
|
||||
disabled={state.saving() || state.discoveryRunRunning()}
|
||||
>
|
||||
@@ -177,7 +177,7 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<div class="grid gap-3 lg:grid-cols-2">
|
||||
<div class="flex items-center gap-3 p-3 rounded-md border border-border bg-surface-alt">
|
||||
<svg
|
||||
class="w-4 h-4 text-muted flex-shrink-0"
|
||||
class="w-4 h-4 text-muted shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -192,12 +192,12 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<label for="ai-cost-budget" class="text-xs font-medium text-base-content">
|
||||
30-day Budget
|
||||
</label>
|
||||
<div class="relative flex-shrink-0">
|
||||
<div class="relative shrink-0">
|
||||
<span class="absolute left-2 top-1/2 -translate-y-1/2 text-muted text-xs">$</span>
|
||||
<input
|
||||
id="ai-cost-budget"
|
||||
type="number"
|
||||
class="w-24 min-h-10 sm:min-h-9 pl-5 pr-2 py-2 text-sm border border-border rounded bg-surface"
|
||||
class="w-24 min-h-10 sm:min-h-9 pl-5 pr-2 py-2 text-sm border border-border rounded-sm bg-surface"
|
||||
value={state.form.costBudgetUSD30d}
|
||||
onInput={(e) => state.setForm('costBudgetUSD30d', e.currentTarget.value)}
|
||||
min={0}
|
||||
@@ -219,7 +219,7 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-3 p-3 rounded-md border border-border bg-surface-alt">
|
||||
<svg
|
||||
class="w-4 h-4 text-muted flex-shrink-0"
|
||||
class="w-4 h-4 text-muted shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -237,7 +237,7 @@ export const AIProviderRuntimeControlsSection: Component<AIRuntimeControlsSectio
|
||||
<input
|
||||
id="ai-request-timeout"
|
||||
type="number"
|
||||
class="w-20 min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded bg-surface"
|
||||
class="w-20 min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded-sm bg-surface"
|
||||
value={state.form.requestTimeoutSeconds}
|
||||
onInput={(e) => {
|
||||
const value = parseInt(e.currentTarget.value, 10);
|
||||
@@ -302,7 +302,7 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
</span>
|
||||
<Show when={state.form.controlLevel !== 'read_only'}>
|
||||
<span
|
||||
class={`px-1.5 py-0.5 text-[10px] font-medium rounded ${getAIControlLevelBadgeClass(state.form.controlLevel)}`}
|
||||
class={`px-1.5 py-0.5 text-[10px] font-medium rounded-sm ${getAIControlLevelBadgeClass(state.form.controlLevel)}`}
|
||||
>
|
||||
{assistantCommandAccessBadgeLabel()}
|
||||
</span>
|
||||
@@ -321,8 +321,8 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
disabled={state.saving()}
|
||||
fieldBaseClass="flex flex-col gap-2 sm:flex-row"
|
||||
fieldClass="sm:items-center sm:gap-3"
|
||||
labelClass="text-xs font-medium text-muted sm:w-28 sm:flex-shrink-0"
|
||||
selectBaseClass="w-full min-w-0 min-h-10 px-2 py-2 text-sm border border-border rounded bg-surface sm:min-h-9 sm:flex-1"
|
||||
labelClass="text-xs font-medium text-muted sm:w-28 sm:shrink-0"
|
||||
selectBaseClass="w-full min-w-0 min-h-10 px-2 py-2 text-sm border border-border rounded-sm bg-surface sm:min-h-9 sm:flex-1"
|
||||
>
|
||||
<option value="read_only">Observe only - Assistant cannot take chat actions</option>
|
||||
<option value="controlled">Ask first - Assistant asks before chat-only actions</option>
|
||||
@@ -332,11 +332,11 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
</option>
|
||||
</Show>
|
||||
</FormSelect>
|
||||
<p class="text-[10px] text-muted sm:ml-[7.5rem]">
|
||||
<p class="text-[10px] text-muted sm:ml-30">
|
||||
{getAIControlLevelDescription(state.form.controlLevel)}
|
||||
</p>
|
||||
<Show when={state.form.controlLevel === 'autonomous'}>
|
||||
<div class="p-2 bg-amber-100 dark:bg-amber-900 rounded border border-amber-200 dark:border-amber-800 text-[10px] text-amber-800 dark:text-amber-200">
|
||||
<div class="p-2 bg-amber-100 dark:bg-amber-900/25 rounded-sm border border-amber-200 dark:border-amber-800 text-[10px] text-amber-800 dark:text-amber-200">
|
||||
<strong>Important:</strong> Assistant may take eligible chat-only actions from this
|
||||
mode. Infrastructure changes stay with Patrol mode. Keep protected guests set for
|
||||
anything Assistant must not touch. See{' '}
|
||||
@@ -368,7 +368,7 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
<div class="flex items-start gap-3 pt-2 border-t border-blue-200 dark:border-blue-700">
|
||||
<label
|
||||
for="ai-protected-guests"
|
||||
class="text-xs font-medium text-muted w-28 flex-shrink-0 pt-1"
|
||||
class="text-xs font-medium text-muted w-28 shrink-0 pt-1"
|
||||
>
|
||||
Protected guests
|
||||
</label>
|
||||
@@ -379,7 +379,7 @@ export const AIAssistantCommandAccessSection: Component<AIRuntimeControlsSection
|
||||
value={state.form.protectedGuests}
|
||||
onInput={(e) => state.setForm('protectedGuests', e.currentTarget.value)}
|
||||
placeholder="e.g., 100, 101, prod-db"
|
||||
class="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded"
|
||||
class="w-full min-h-10 sm:min-h-9 px-2 py-2 text-sm border border-border rounded-sm"
|
||||
disabled={state.saving()}
|
||||
/>
|
||||
<p class="text-[10px] text-muted mt-1">
|
||||
|
||||
@@ -142,7 +142,7 @@ const PatrolSettingsContent: Component<{ state: ReturnType<typeof useAISettingsS
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex min-h-10 items-center justify-center rounded-md border border-blue-300 px-3 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50 dark:border-blue-700 dark:text-blue-300 dark:hover:bg-blue-900 sm:min-h-9"
|
||||
class="inline-flex min-h-10 items-center justify-center rounded-md border border-blue-300 px-3 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50 dark:border-blue-700 dark:text-blue-300 dark:hover:bg-blue-900/25 sm:min-h-9"
|
||||
onClick={() => navigate(PATROL_PATH)}
|
||||
>
|
||||
Open Patrol
|
||||
@@ -342,7 +342,7 @@ const AssistantSettingsContent: Component<{ state: ReturnType<typeof useAISettin
|
||||
<AIModelOverrideField state={props.state} kind="assistant" />
|
||||
<AIAssistantCommandAccessSection state={props.state} />
|
||||
<AIChatMaintenanceSection state={props.state} />
|
||||
<details class="overflow-hidden rounded-md border border-border bg-surface-alt/40">
|
||||
<details class="overflow-hidden rounded-md border border-border">
|
||||
<summary class="cursor-pointer list-none px-4 py-3 text-sm font-semibold text-base-content hover:bg-surface-hover">
|
||||
Service identification
|
||||
<span class="ml-2 font-normal text-muted">
|
||||
@@ -459,7 +459,7 @@ export const AISettings: Component<{ page?: AISettingsPage }> = (props) => {
|
||||
>
|
||||
<div class="flex items-center gap-2 text-sm text-red-700 dark:text-red-300">
|
||||
<svg
|
||||
class="h-4 w-4 flex-shrink-0"
|
||||
class="h-4 w-4 shrink-0"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -475,7 +475,7 @@ export const AISettings: Component<{ page?: AISettingsPage }> = (props) => {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-shrink-0 px-3 py-1.5 text-sm font-medium text-red-700 dark:text-red-300 border border-red-300 dark:border-red-700 rounded-md hover:bg-red-100 dark:hover:bg-red-900/50"
|
||||
class="shrink-0 px-3 py-1.5 text-sm font-medium text-red-700 dark:text-red-300 border border-red-300 dark:border-red-700 rounded-md hover:bg-red-100 dark:hover:bg-red-900/50"
|
||||
onClick={() => state.loadSettings()}
|
||||
>
|
||||
{getAISettingsRetryLabel()}
|
||||
|
||||
@@ -45,7 +45,7 @@ export const AISettingsStatusAndActions: Component<AISettingsStatusAndActionsPro
|
||||
<Show when={props.showConnectionControls && state.settings()?.configured}>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-4 py-2.5 text-sm border border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-4 py-2.5 text-sm border border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900/25 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={state.handleTest}
|
||||
disabled={state.testing() || state.saving() || state.loading()}
|
||||
>
|
||||
|
||||
@@ -217,7 +217,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
<span
|
||||
class={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${
|
||||
isWildcard
|
||||
? 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200'
|
||||
? 'bg-amber-100 text-amber-800 dark:bg-amber-900/25 dark:text-amber-200'
|
||||
: 'bg-surface-alt text-base-content'
|
||||
}`}
|
||||
title={scope.value}
|
||||
@@ -259,7 +259,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
>
|
||||
<span class="text-muted">{usageSummary}</span>
|
||||
<Show when={agentUsageEntry && agentUsageEntry.count > 1}>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-800 dark:bg-amber-900 dark:text-amber-200">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-800 dark:bg-amber-900/25 dark:text-amber-200">
|
||||
<svg class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -288,7 +288,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
data-api-token-action="edit-scopes"
|
||||
onClick={(event) => openScopeEditor(token, event.currentTarget)}
|
||||
disabled={!canManage()}
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900 dark:hover:text-blue-200 ${
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900/25 dark:hover:text-blue-200 ${
|
||||
compact ? 'flex-1 border border-blue-200 dark:border-blue-800' : 'sm:min-h-9'
|
||||
}`}
|
||||
>
|
||||
@@ -300,7 +300,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
data-api-token-action="rename"
|
||||
onClick={(event) => openRenameDialog(token, event.currentTarget)}
|
||||
disabled={!canManage()}
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900 dark:hover:text-blue-200 ${
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 hover:text-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-blue-300 dark:hover:bg-blue-900/25 dark:hover:text-blue-200 ${
|
||||
compact ? 'flex-1 border border-blue-200 dark:border-blue-800' : 'sm:min-h-9'
|
||||
}`}
|
||||
>
|
||||
@@ -312,8 +312,8 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
data-api-token-action="revoke"
|
||||
onClick={(event) => openRevokeDialog(token, event.currentTarget)}
|
||||
disabled={!canManage()}
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-red-600 transition hover:bg-red-50 hover:text-red-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-red-400 dark:hover:bg-red-900 dark:hover:text-red-300 ${
|
||||
compact ? 'flex-1 border border-red-200 dark:border-red-900' : 'sm:min-h-9'
|
||||
class={`inline-flex min-h-10 items-center justify-center rounded-md px-2.5 py-1.5 text-sm font-semibold text-red-600 transition hover:bg-red-50 hover:text-red-700 disabled:cursor-not-allowed disabled:opacity-60 dark:text-red-400 dark:hover:bg-red-900/25 dark:hover:text-red-300 ${
|
||||
compact ? 'flex-1 border border-red-200 dark:border-red-900/25' : 'sm:min-h-9'
|
||||
}`}
|
||||
>
|
||||
Revoke
|
||||
@@ -323,7 +323,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div class="space-y-5">
|
||||
<Card padding="none" class="border border-border shadow-sm">
|
||||
<Card padding="none" class="border border-border shadow-xs">
|
||||
<div class="flex flex-col gap-6 p-4 sm:p-6 lg:p-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
@@ -342,7 +342,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
type="button"
|
||||
onClick={focusCreateSection}
|
||||
disabled={!canManage()}
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 focus-visible:ring-offset-white"
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-xs transition hover:bg-blue-700 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 focus-visible:ring-offset-white"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
@@ -369,14 +369,14 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-sm">
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-xs">
|
||||
<div class="text-[0.7rem] font-semibold uppercase tracking-wide text-muted">
|
||||
Total tokens
|
||||
</div>
|
||||
<div class="mt-1 text-2xl font-semibold text-base-content">{totalTokens()}</div>
|
||||
<p class="mt-1 text-xs text-muted">Stored credentials across all agents</p>
|
||||
</div>
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-sm">
|
||||
<div class="rounded-md border border-border p-4 text-sm shadow-xs">
|
||||
<div class="text-[0.7rem] font-semibold uppercase tracking-wide text-muted">
|
||||
Scoped tokens
|
||||
</div>
|
||||
@@ -384,7 +384,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
<p class="mt-1 text-xs text-muted">Limited access tokens with defined scopes</p>
|
||||
</div>
|
||||
<div
|
||||
class={`rounded-md border p-4 text-sm shadow-sm ${hasWildcardTokens() ? 'border-amber-300 bg-amber-50 text-amber-900 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-100' : 'border-border bg-surface text-base-content'}`}
|
||||
class={`rounded-md border p-4 text-sm shadow-xs ${hasWildcardTokens() ? 'border-amber-300 bg-amber-50 text-amber-900/25 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-100' : 'border-border bg-surface text-base-content'}`}
|
||||
>
|
||||
<div
|
||||
class={`text-[0.7rem] font-semibold uppercase tracking-wide ${
|
||||
@@ -440,7 +440,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
<button
|
||||
onClick={reopenTokenDialog}
|
||||
disabled={!canManage()}
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900 dark:hover:text-green-100"
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900/25 dark:hover:text-green-100"
|
||||
>
|
||||
Show
|
||||
</button>
|
||||
@@ -448,7 +448,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
onClick={() => {
|
||||
dismissNewToken();
|
||||
}}
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900 dark:hover:text-green-100"
|
||||
class="font-medium underline decoration-green-500 underline-offset-2 hover:text-green-900/25 dark:hover:text-green-100"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
@@ -461,20 +461,20 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
newTokenRecord()?.scopes?.[0] === MONITORING_READ_SCOPE
|
||||
}
|
||||
>
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm dark:border-blue-800 dark:bg-blue-900 dark:text-blue-100">
|
||||
<div class="rounded-md border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900/25 shadow-xs dark:border-blue-800 dark:bg-blue-900/25 dark:text-blue-100">
|
||||
<div class="mb-2 font-semibold">Magic Kiosk Link</div>
|
||||
<p class="mb-3 text-xs text-blue-700 dark:text-blue-300">
|
||||
Use this link to open Pulse directly in Kiosk mode without logging in. Perfect for
|
||||
wall displays and digital signage.
|
||||
</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="flex-1 rounded border border-blue-200 bg-surface px-3 py-2 font-mono text-xs text-blue-800 dark:border-blue-800 dark:bg-black dark:text-blue-200 break-all">
|
||||
<code class="flex-1 rounded-sm border border-blue-200 bg-surface px-3 py-2 font-mono text-xs text-blue-800 dark:border-blue-800 dark:bg-black dark:text-blue-200 break-all">
|
||||
{newMonitoringKioskLink()}
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void copyNewMonitoringKioskLink()}
|
||||
class="flex-shrink-0 rounded-md bg-blue-600 px-3 py-2 text-xs font-semibold text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 dark:bg-blue-600 dark:hover:bg-blue-500"
|
||||
class="shrink-0 rounded-md bg-blue-600 px-3 py-2 text-xs font-semibold text-white shadow-xs hover:bg-blue-700 focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 dark:bg-blue-600 dark:hover:bg-blue-500"
|
||||
>
|
||||
Copy Link
|
||||
</button>
|
||||
@@ -516,7 +516,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
type="button"
|
||||
onClick={focusCreateSection}
|
||||
disabled={!canManage()}
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md border border-blue-200 px-3 py-2 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 dark:border-blue-700 dark:text-blue-200 dark:hover:bg-blue-900"
|
||||
class="inline-flex min-h-10 sm:min-h-10 items-center gap-2 rounded-md border border-blue-200 px-3 py-2 text-sm font-semibold text-blue-600 transition hover:bg-blue-50 dark:border-blue-700 dark:text-blue-200 dark:hover:bg-blue-900/25"
|
||||
>
|
||||
Generate new
|
||||
</button>
|
||||
@@ -535,7 +535,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
>
|
||||
<div class="flex min-w-0 items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<h5 class="break-words text-sm font-semibold text-base-content">
|
||||
<h5 class="wrap-break-word text-sm font-semibold text-base-content">
|
||||
{token.name || 'Untitled'}
|
||||
</h5>
|
||||
<div class="mt-1 font-mono text-xs text-muted">{tokenHint(token)}</div>
|
||||
@@ -641,7 +641,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
id={API_TOKEN_CREATE_ANCHOR}
|
||||
padding="none"
|
||||
class={`border border-border transition-shadow ${
|
||||
createHighlight() ? 'ring-2 ring-blue-500 shadow-sm' : ''
|
||||
createHighlight() ? 'ring-2 ring-blue-500 shadow-xs' : ''
|
||||
}`}
|
||||
ref={setCreateSectionRef}
|
||||
>
|
||||
@@ -661,7 +661,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
? undefined
|
||||
: 'Choose a scope preset or custom scope before generating a token.'
|
||||
}
|
||||
class="inline-flex min-h-11 w-full items-center justify-center rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60 sm:min-h-10 sm:w-auto dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
class="inline-flex min-h-11 w-full items-center justify-center rounded-md bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-xs transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60 sm:min-h-10 sm:w-auto dark:bg-blue-500 dark:hover:bg-blue-400"
|
||||
>
|
||||
{isGenerating() ? 'Generating…' : 'Generate'}
|
||||
</button>
|
||||
@@ -682,7 +682,7 @@ export const APITokenManager: Component<APITokenManagerProps> = (props) => {
|
||||
onInput={(e) => setNameInput(e.currentTarget.value)}
|
||||
placeholder={API_TOKEN_NAME_PLACEHOLDER}
|
||||
disabled={!canManage()}
|
||||
class="w-full min-h-10 sm:min-h-10 rounded-md border border-border bg-surface px-3 py-2.5 text-sm text-base-content shadow-sm transition focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-500"
|
||||
class="w-full min-h-10 sm:min-h-10 rounded-md border border-border bg-surface px-3 py-2.5 text-sm text-base-content shadow-xs transition focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,16 +44,16 @@ export const APITokenManagerDialogs: Component<APITokenManagerDialogsProps> = (p
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label class="flex cursor-pointer items-start gap-3 rounded-md border border-amber-300 bg-amber-50 p-3 text-sm dark:border-amber-700 dark:bg-amber-900">
|
||||
<label class="flex cursor-pointer items-start gap-3 rounded-md border border-amber-300 bg-amber-50 p-3 text-sm dark:border-amber-700 dark:bg-amber-900/25">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.editScopes().includes('*')}
|
||||
onChange={() => props.onToggleEditScope('*')}
|
||||
disabled={props.updatingTokenId() !== null}
|
||||
class="mt-0.5 h-4 w-4 rounded border-border text-amber-600 focus:ring-amber-500"
|
||||
class="mt-0.5 h-4 w-4 rounded-sm border-border text-amber-600 focus:ring-amber-500"
|
||||
/>
|
||||
<span>
|
||||
<span class="block font-semibold text-amber-900 dark:text-amber-100">
|
||||
<span class="block font-semibold text-amber-900/25 dark:text-amber-100">
|
||||
Full access
|
||||
</span>
|
||||
<span class="text-amber-800 dark:text-amber-200">
|
||||
@@ -78,7 +78,7 @@ export const APITokenManagerDialogs: Component<APITokenManagerDialogsProps> = (p
|
||||
checked={props.editScopes().includes(option.value)}
|
||||
onChange={() => props.onToggleEditScope(option.value)}
|
||||
disabled={props.updatingTokenId() !== null}
|
||||
class="mt-0.5 h-4 w-4 rounded border-border text-blue-600 focus:ring-blue-500"
|
||||
class="mt-0.5 h-4 w-4 rounded-sm border-border text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span>
|
||||
<span class="block font-medium text-base-content">{option.label}</span>
|
||||
@@ -151,7 +151,7 @@ export const APITokenManagerDialogs: Component<APITokenManagerDialogsProps> = (p
|
||||
value={props.renameInput()}
|
||||
onInput={(event) => props.onRenameInput(event.currentTarget.value)}
|
||||
disabled={props.isRenaming()}
|
||||
class="w-full min-h-10 rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200"
|
||||
class="w-full min-h-10 rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200"
|
||||
autofocus
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -204,7 +204,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
title="External agents"
|
||||
noPadding
|
||||
class={`scroll-mt-20 transition-shadow ${
|
||||
panelHighlight() ? 'ring-2 ring-blue-500 shadow-sm' : ''
|
||||
panelHighlight() ? 'ring-2 ring-blue-500 shadow-xs' : ''
|
||||
}`}
|
||||
>
|
||||
<span id={PULSE_MCP_SETUP_ANCHOR} class="sr-only" aria-hidden="true" />
|
||||
@@ -294,7 +294,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</li>
|
||||
</ol>
|
||||
<details
|
||||
class="rounded-md border border-border bg-surface-alt/40 p-3"
|
||||
class="rounded-md border border-border p-3"
|
||||
onToggle={(event) => setInstallerCommandsOpen(event.currentTarget.open)}
|
||||
>
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
@@ -326,7 +326,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</Show>
|
||||
</details>
|
||||
<details
|
||||
class="rounded-md border border-border bg-surface-alt/40 p-3"
|
||||
class="rounded-md border border-border p-3"
|
||||
onToggle={(event) => setClientConfigOpen(event.currentTarget.open)}
|
||||
>
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
@@ -379,7 +379,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</details>
|
||||
<Show when={hasClientBuilderDetails()}>
|
||||
<details
|
||||
class="rounded-md border border-border bg-surface-alt/40 p-3"
|
||||
class="rounded-md border border-border p-3"
|
||||
onToggle={(event) => setAdvancedClientDetailsOpen(event.currentTarget.open)}
|
||||
>
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
@@ -405,7 +405,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
/>
|
||||
<span>
|
||||
External agents expose {posture().label}{' '}
|
||||
<span class="text-muted/80">through Patrol mode</span>
|
||||
<span class="">through Patrol mode</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -420,7 +420,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
Built-in Pulse views and connected clients all sit behind the same
|
||||
Patrol policy. Connected agents do not get separate powers.
|
||||
</p>
|
||||
<ul class="divide-y divide-border rounded-md border border-border bg-surface-alt/40">
|
||||
<ul class="divide-y divide-border rounded-md border border-border">
|
||||
<For each={surfaceContractEntries()}>
|
||||
{(entry) => (
|
||||
<li class="space-y-1 p-3">
|
||||
@@ -460,7 +460,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
build clients against this instance. They do not change Patrol mode.
|
||||
</p>
|
||||
<Show when={workflowPrompts().length > 0}>
|
||||
<details class="rounded-md border border-border bg-surface-alt/40 p-3">
|
||||
<details class="rounded-md border border-border p-3">
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
<span>Agent starting points</span>{' '}
|
||||
<span class="ml-2 text-xs font-normal text-muted">
|
||||
@@ -481,7 +481,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
prompt.name === AGENT_WORKFLOW_PROMPT_OPERATIONS_LOOP
|
||||
}
|
||||
>
|
||||
<span class="rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300">
|
||||
<span class="rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/25 dark:text-emerald-300">
|
||||
Patrol
|
||||
</span>
|
||||
</Show>
|
||||
@@ -532,7 +532,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={errorCodeSummaries().length > 0}>
|
||||
<details class="rounded-md border border-border bg-surface-alt/40 p-3 text-xs text-muted">
|
||||
<details class="rounded-md border border-border p-3 text-xs text-muted">
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
Failure codes{' '}
|
||||
<span class="ml-2 text-xs font-normal text-muted">
|
||||
@@ -571,7 +571,7 @@ export const AgentIntegrationsPanel: Component = () => {
|
||||
design notes.
|
||||
</p>
|
||||
<Show when={manifest() && grouped().length > 0}>
|
||||
<details class="rounded-md border border-border bg-surface-alt/40 p-3">
|
||||
<details class="rounded-md border border-border p-3">
|
||||
<summary class="cursor-pointer text-sm font-semibold text-base-content">
|
||||
Agent capabilities{' '}
|
||||
<span class="ml-2 text-xs font-normal text-muted">
|
||||
|
||||
@@ -87,7 +87,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
<Show when={showUpgradePrompts()}>
|
||||
<Card padding="lg" class="space-y-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-md bg-amber-100 dark:bg-amber-900">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-md bg-amber-100 dark:bg-amber-900/25">
|
||||
<Crown class="w-5 h-5 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -198,7 +198,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleEdit(profile)}
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-blue-600 hover:bg-blue-50 sm:min-h-0 sm:min-w-0 dark:hover:text-blue-400 dark:hover:bg-blue-900"
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-blue-600 hover:bg-blue-50 sm:min-h-0 sm:min-w-0 dark:hover:text-blue-400 dark:hover:bg-blue-900/25"
|
||||
title="Edit profile"
|
||||
>
|
||||
<Pencil class="w-4 h-4" />
|
||||
@@ -206,7 +206,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDelete(profile)}
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-red-600 hover:bg-red-50 sm:min-h-0 sm:min-w-0 dark:hover:text-red-400 dark:hover:bg-red-900"
|
||||
class="inline-flex min-h-11 min-w-11 items-center justify-center rounded-md p-1.5 hover:text-red-600 hover:bg-red-50 sm:min-h-0 sm:min-w-0 dark:hover:text-red-400 dark:hover:bg-red-900/25"
|
||||
title="Delete profile"
|
||||
>
|
||||
<Trash2 class="w-4 h-4" />
|
||||
@@ -268,7 +268,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
fieldBaseClass="contents"
|
||||
value={assignment()?.profile_id || ''}
|
||||
onChange={(e) => handleAssign(agent.assignmentId, e.currentTarget.value)}
|
||||
selectBaseClass="min-h-10 sm:min-h-9 w-full sm:max-w-xs rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
selectBaseClass="min-h-10 sm:min-h-9 w-full sm:max-w-xs rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">No profile</option>
|
||||
<Show
|
||||
@@ -369,7 +369,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
value={formName()}
|
||||
onInput={(e) => setFormName(e.currentTarget.value)}
|
||||
placeholder="e.g., Production Servers"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800"
|
||||
class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -386,7 +386,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
onInput={(e) => setFormDescription(e.currentTarget.value)}
|
||||
placeholder="What is this profile for?"
|
||||
rows={2}
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800 resize-none"
|
||||
textareaBaseClass="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-200 dark:focus:border-blue-400 dark:focus:ring-blue-800 resize-none"
|
||||
/>
|
||||
|
||||
{/* Settings */}
|
||||
@@ -447,7 +447,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
onChange={(e) =>
|
||||
updateSetting(setting.key, e.currentTarget.value || undefined)
|
||||
}
|
||||
selectBaseClass="min-h-10 sm:min-h-9 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
selectBaseClass="min-h-10 sm:min-h-9 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Default</option>
|
||||
<For each={(setting as SelectSetting).options}>
|
||||
@@ -464,7 +464,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
updateSetting(setting.key, e.currentTarget.value || undefined)
|
||||
}
|
||||
placeholder="30s"
|
||||
class="min-h-10 sm:min-h-9 w-24 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="min-h-10 sm:min-h-9 w-24 rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={setting.type === 'string'}>
|
||||
@@ -476,7 +476,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
updateSetting(setting.key, e.currentTarget.value || undefined)
|
||||
}
|
||||
placeholder={(setting as StringSetting).placeholder || ''}
|
||||
class="min-h-10 sm:min-h-9 w-40 rounded-md border bg-surface px-2.5 py-1.5 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="min-h-10 sm:min-h-9 w-40 rounded-md border bg-surface px-2.5 py-1.5 text-sm shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -493,7 +493,7 @@ export const AgentProfilesPanel: Component = () => {
|
||||
</p>
|
||||
<For each={unknownKeys()}>
|
||||
{(key) => (
|
||||
<div class="rounded-md border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 p-3 mb-2">
|
||||
<div class="rounded-md border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/25 p-3 mb-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-base-content font-mono">
|
||||
{key}
|
||||
@@ -512,12 +512,12 @@ export const AgentProfilesPanel: Component = () => {
|
||||
updateSetting(key, val || undefined);
|
||||
}
|
||||
}}
|
||||
class="min-h-10 sm:min-h-9 w-32 rounded-md border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="min-h-10 sm:min-h-9 w-32 rounded-md border bg-surface px-2.5 py-1.5 text-sm text-base-content shadow-xs focus:border-blue-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateSetting(key, undefined)}
|
||||
class="inline-flex min-h-10 min-w-10 sm:min-h-9 sm:min-w-9 items-center justify-center rounded text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900"
|
||||
class="inline-flex min-h-10 min-w-10 sm:min-h-9 sm:min-w-9 items-center justify-center rounded-sm text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/25"
|
||||
title="Remove this setting"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -324,7 +324,7 @@ export default function AuditLogPanel() {
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
class="p-4 bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-800 rounded-md text-red-700 dark:text-red-300"
|
||||
class="p-4 bg-red-50 dark:bg-red-900/25 border border-red-200 dark:border-red-800 rounded-md text-red-700 dark:text-red-300"
|
||||
>
|
||||
{error()}
|
||||
</div>
|
||||
@@ -402,7 +402,7 @@ export default function AuditLogPanel() {
|
||||
render: (event) => (
|
||||
<div class="min-w-0">
|
||||
<span
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${getAuditEventTypeBadgeClass(event.event)}`}
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium ${getAuditEventTypeBadgeClass(event.event)}`}
|
||||
>
|
||||
{event.event}
|
||||
</span>
|
||||
@@ -454,7 +454,7 @@ export default function AuditLogPanel() {
|
||||
when={isVerifying}
|
||||
fallback={
|
||||
<span
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${badge.className}`}
|
||||
class={`inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium ${badge.className}`}
|
||||
>
|
||||
{badge.label}
|
||||
</span>
|
||||
|
||||
@@ -105,7 +105,7 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
<button
|
||||
onClick={() => setUrlToRemove(url)}
|
||||
disabled={!canManage()}
|
||||
class="p-2 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900 rounded-md transition-colors"
|
||||
class="p-2 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/25 rounded-md transition-colors"
|
||||
title="Remove webhook endpoint"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
@@ -147,11 +147,11 @@ export const AuditWebhookPanel: Component<AuditWebhookPanelProps> = (props) => {
|
||||
</SettingsPanel>
|
||||
<Card tone="warning" class="border border-amber-200 dark:border-amber-800">
|
||||
<div class="p-5 flex gap-4">
|
||||
<div class="p-3 bg-amber-100 dark:bg-amber-900 rounded-md h-fit text-amber-600 dark:text-amber-300">
|
||||
<div class="p-3 bg-amber-100 dark:bg-amber-900/25 rounded-md h-fit text-amber-600 dark:text-amber-300">
|
||||
<Shield size={22} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-amber-900 dark:text-amber-100 mb-1.5">
|
||||
<h3 class="text-base font-semibold text-amber-900/25 dark:text-amber-100 mb-1.5">
|
||||
{AUDIT_WEBHOOK_SECURITY_NOTE_TITLE}
|
||||
</h3>
|
||||
<p class="text-sm text-amber-800 dark:text-amber-200 leading-relaxed">
|
||||
|
||||
@@ -226,7 +226,7 @@ export const AvailabilitySettingsPanel: Component = () => {
|
||||
{(message) => (
|
||||
<div
|
||||
role="alert"
|
||||
class="border-b border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200"
|
||||
class="border-b border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:border-rose-900/25 dark:bg-rose-950/25 dark:text-rose-200"
|
||||
>
|
||||
{message()}
|
||||
</div>
|
||||
@@ -246,7 +246,7 @@ export const AvailabilitySettingsPanel: Component = () => {
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="mdCompact"
|
||||
class="min-h-9 gap-2"
|
||||
class="min-h-9! gap-2"
|
||||
onClick={() => openAddDialog('service')}
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
|
||||
@@ -124,10 +124,10 @@ export const BackupTransferDialogs: Component<BackupTransferDialogsProps> = (pro
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-50 dark:bg-amber-900 border border-amber-200 dark:border-amber-800 rounded-md p-3">
|
||||
<div class="bg-amber-50 dark:bg-amber-900/25 border border-amber-200 dark:border-amber-800 rounded-md p-3">
|
||||
<div class="flex gap-2">
|
||||
<svg
|
||||
class="w-4 h-4 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5"
|
||||
class="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -206,7 +206,7 @@ export const BackupTransferDialogs: Component<BackupTransferDialogsProps> = (pro
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-muted rounded p-2">
|
||||
<div class="text-xs text-muted rounded-sm p-2">
|
||||
<p class="font-semibold mb-1">{getAPITokenManagementLocationMessage()}</p>
|
||||
<p>
|
||||
Tokens are managed in the UI and stored in <code>api_tokens.json</code>.
|
||||
@@ -279,7 +279,7 @@ export const BackupTransferDialogs: Component<BackupTransferDialogsProps> = (pro
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-800 rounded p-3">
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/25 border border-yellow-200 dark:border-yellow-800 rounded-sm p-3">
|
||||
<div class="space-y-2 text-xs text-yellow-700 dark:text-yellow-300">
|
||||
<p>
|
||||
<strong>Warning:</strong> Importing will replace all current configuration. This
|
||||
|
||||
@@ -55,7 +55,7 @@ export const BillingAdminOrganizationsTable: Component<BillingAdminOrganizations
|
||||
<div class="text-xs text-muted">
|
||||
<span class="font-mono">{org.org_id}</span>
|
||||
{getBillingAdminOrganizationBadges(org).map((badge) => (
|
||||
<span class={`ml-2 rounded px-1.5 py-0.5 ${badge.badgeClass}`}>
|
||||
<span class={`ml-2 rounded-sm px-1.5 py-0.5 ${badge.badgeClass}`}>
|
||||
{badge.label}
|
||||
</span>
|
||||
))}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user