Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0bc522a
Move the workspace building blocks out of CloudNativePG
nadaverell Oct 3, 2026
78d35ba
Move Prometheus series-scope isolation out of the CNPG history file
nadaverell Oct 3, 2026
10eea38
Name the informer cache-scope intersection for what it is
nadaverell Oct 3, 2026
7a5912e
Share workspace screen primitives between Capacity and CloudNativePG
nadaverell Oct 3, 2026
4aa1604
Move per-kind access and coverage out of the CNPG workspace
nadaverell Oct 3, 2026
afc3ed4
Read grants as structured objects in the client
nadaverell Oct 3, 2026
ba0d14f
Report namespaces Radar does not cache as uncached, not denied
nadaverell Oct 3, 2026
fad04d4
Move the action contract out of the CloudNativePG actions
nadaverell Oct 3, 2026
cd42b72
Send grants as structured objects instead of worded strings
nadaverell Oct 3, 2026
7643c56
Share one read-outcome shape across HA, recovery and storage
nadaverell Oct 3, 2026
a73adfb
Gate the CloudNativePG workspace on the Radar that serves it
nadaverell Oct 3, 2026
afbe260
Share the bounded per-namespace fan-out of the CNPG fleet reads
nadaverell Oct 3, 2026
bf05ebd
Title a CloudNativePG problem the way the Issues page titles its reason
nadaverell Oct 3, 2026
a76625d
Name each CloudNativePG object's GitOps manager in the workspace
nadaverell Oct 3, 2026
b2242ba
Share the reviewed-action client between workspace integrations
nadaverell Oct 3, 2026
9f0aebe
Write the rules for unknown and partial values once
nadaverell Oct 3, 2026
dd5590f
Say a namespace is not cached rather than not accessible
nadaverell Oct 3, 2026
2f518e9
Merge the backend workspace kit
nadaverell Oct 3, 2026
289c1a5
Show the GitOps manager the server detected for CloudNativePG objects
nadaverell Oct 3, 2026
3508fc5
Recompute the declarations list when the detected managers change
nadaverell Oct 3, 2026
b00308d
Document how to build a workspace integration from the shared pieces
nadaverell Oct 3, 2026
3230bf3
List the workspace and CloudNativePG directories in the structure map
nadaverell Oct 3, 2026
6a99aaa
Keep the standard views until a Radar confirms the CloudNativePG work…
nadaverell Oct 3, 2026
2c6299a
Name both causes of an unread namespace in empty lists and notices
nadaverell Oct 3, 2026
8b034f0
Drop a comment that restates worseTone's name
nadaverell Oct 3, 2026
b2ac729
Name the shared view pieces for what they are, not for workspaces
nadaverell Oct 4, 2026
5af6ed1
Call CloudNativePG's screens views, not a workspace
nadaverell Oct 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Not everything is in this file. The following files contain critical details tha
| Adding or modifying **HTTP endpoints** | `internal/server/server.go` — all routes are defined here — **plus** the handler's doc comments (why the route is gated the way it is lives there; copy the gate of the closest sibling only after reading it) and the integration's section in [docs/integrations.md](docs/integrations.md) |
| Adding or modifying **CLI flags** | `cmd/explorer/main.go` — flag definitions and defaults |
| Adding a **new CRD integration** (renderer, topology, discovery) | [docs/INTEGRATION_GUIDE.md](docs/INTEGRATION_GUIDE.md) — full checklist with collision gotchas |
| Building a **workspace integration** (several related CRDs with their own screens, like `/cnpg` or `/capacity`) | [docs/INTEGRATION_GUIDE.md](docs/INTEGRATION_GUIDE.md#3-workspace-integrations) — the shared server, UI and action pieces to import, and what is not shared yet; [DESIGN.md](DESIGN.md#unknown-partial-and-denied-values) — how unknown, partial and denied values read |
| Working on the **CloudNativePG workspace** (`/cnpg`) | [docs/cnpg.md](docs/cnpg.md) — destinations, navigation (drawer trail, return label, `ctx` guard) and the certainty table: which source each fact comes from and what it reads when unknown. Data from `/api/cnpg/workspace` (per-kind coverage); derivations in `packages/k8s-ui/src/components/cnpg/workspace.ts` + `relations.ts`; screens in `web/src/components/cnpg/` |
| Working on **local per-cluster integration settings** (Metrics, Argo CD, Cost in `~/.radar/clusters.json`) | [docs/configuration.md](docs/configuration.md#local-integration-connections) — store `internal/config/profiles.go`, resolve/update `internal/connections`, activation `internal/connectionruntime`, routes `GET/PUT /api/integrations/connections`. In local mode the older `PUT /api/integrations/{prometheus,argocd,cost}` return 409 |
| Working on **GitOps** (Argo CD / Flux detail pages, operations, Terminating lifecycle, drift, per-resource health, remote destinations) | [docs/gitops.md](docs/gitops.md) — detail-page tabs, operation semantics, the Terminating severity ramp, nested navigation, single-cluster scope. Engine in `pkg/gitops/`, handlers `internal/server/gitops_handlers.go` |
Expand Down
12 changes: 12 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Standard Tailwind type scale. No custom sizes or tracking. Use Tailwind utilitie
| `.btn-brand` | Primary CTAs — brand-colored bg, white text, 10px radius |
| `.btn-brand-muted` | Secondary brand actions — dimmed brand bg, white text |
| `.btn-brand-toggle` | Toggle buttons — 50% brand bg, primary text |
| `.btn-secondary` | Secondary actions beside a `.btn-brand` — bordered surface bg, primary text, 10px radius |

Hover/disabled states are built into the classes. For non-brand buttons, use shadcn/ui `<Button>` variants.

Expand Down Expand Up @@ -172,6 +173,17 @@ Use CSS classes from `components.css` for status cells in table rows:
| `.border-r-subtle` | Right border |
| `.border-t-subtle` | Top border |

### Unknown, partial and denied values
Radar shows only what the cluster reports, and says where it came from. These rules apply to every surface that reads several sources at once (workspace integrations such as Capacity and CloudNativePG, multi-source detail pages):

- **Unavailable ≠ zero.** A value Radar could not read (no access, not installed, not cached, the request failed) renders as unread, naming why — never as `0`, "none" or a healthy colour. A missing grant is named exactly (`GrantText`, `formatGrant`).
- **Partial ≠ exact.** A count or total over data read only in part is a lower bound: `≥N` (`CertaintyGlyph`, `SidebarCategoryDestination.countLowerBound`), and a zero over partial data is unknown, not none.
- **Unread is listed, not left out.** A summary line names what it could not read ("not read: Pods, zones"); a combined tone is never calmer than a part that was not read (`worseTone` ranks `unknown` above `healthy`).
- **Recorded ≠ observed.** A value copied from a status field, an annotation or a declaration says so; a value matched to its subject by name rather than by identity says that too.
- **Facts keep their rows.** `FactRow` renders the unread text in place; never hide a row because its value is missing (unlike `Property`, which hides empty values).

The shared pieces live in `packages/k8s-ui/src/components/facts/` (facts, certainty, GitOps manager), `components/problems/` (problems with their sources), `components/ui/FoldSection.tsx` (section headings and folded sections) and `web/src/components/workspace/` (workspace screen layout and tables); each integration's own doc lists which source each value comes from and how it reads when unknown.

## 5. Layout Principles

### Spacing
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ View TLS certificate details and expiry dates across all namespaces — catch ex

### GitOps

Monitor, diagnose, and manage FluxCD and ArgoCD resources from a dedicated GitOps workspace.
Monitor, diagnose, and manage FluxCD and ArgoCD resources in one place.

<p align="center">
<img src="docs/screenshots/gitops-view.png" alt="GitOps fleet view" width="800">
Expand Down Expand Up @@ -548,7 +548,7 @@ Upgrade impact also gets list-only access to CSIStorageCapacities, FlowSchemas,
| **Strimzi** | [KafkaConnector failure evidence](docs/integrations.md#strimzi-kafka-connectors) (connector/task status) |
| **Velero** | Backup, Restore, Schedule, BackupStorageLocation, VolumeSnapshotLocation |
| **External Secrets** | ExternalSecret, ClusterExternalSecret, SecretStore, ClusterSecretStore |
| **CloudNativePG** | Cluster, Backup, ScheduledBackup, Pooler, Database, Publication, Subscription, ImageCatalog, ClusterImageCatalog, ObjectStore — plus a [workspace](docs/cnpg.md) for fleet, protection and declaration triage |
| **CloudNativePG** | Cluster, Backup, ScheduledBackup, Pooler, Database, Publication, Subscription, ImageCatalog, ClusterImageCatalog, ObjectStore — plus [dedicated views](docs/cnpg.md) for fleet, protection and declaration triage |
| **Crossplane** | Managed Resources (any provider), Composite Resources, Claims, Provider, ProviderConfig, Function, Configuration, Composition, CompositionRevision, XRD |
| **Kyverno** | Policy, ClusterPolicy, PolicyReport, ClusterPolicyReport |
| **Sealed Secrets** | SealedSecret |
Expand Down
108 changes: 107 additions & 1 deletion docs/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,113 @@ shared renderers.
- `packages/k8s-ui/src/components/topology/`: `TopologyFilterSidebar.tsx`,
`K8sResourceNode.tsx`, `layout.ts`, `topology.css`.

## 3. Before submitting
## 3. Workspace integrations

A workspace is a set of screens for several related kinds (CloudNativePG at
`/cnpg`, Karpenter at `/capacity`). Build one only when the operator question
spans objects: fleet health, a chain such as Backup → ObjectStore → Cluster, or
actions that need facts from more than one object. With fewer kinds, or no
cross-object question, a renderer plus Issues plus the detail slots is enough.
The batch kinds, for example, use only the detail seams.

The pieces below are shared and should be imported, not copied. Read
[DESIGN.md](../DESIGN.md#unknown-partial-and-denied-values) for the rules on
unknown, partial and denied values first: every piece here exists to keep them.

- [ ] **Placement.** Under Resources as a category workspace
(`ResourcesSidebar` `categoryWorkspaces`, keyed by the category name from
`api-resources.ts`), or a top-level page when the subject is cluster-wide.
Say whether the namespace filter applies, and why.
- [ ] **Routes and navigation.** Use `/x`, `/x/<screen>` and
`/x/<plural>/<ns|_>/<name>?ctx=`.
- The in-drawer trail is `?drawer=`, encoded by `web/src/utils/drawer-trail.ts`.
- Back labels come from `currentPageLabel` and subject-filtered Issues links
from `issuesPathForSubject` (both in `web/src/utils/page-links.ts`).
- Pin `ctx` on a detail page. After a context switch, say the object is not in
this context; never open a same-named object from another cluster.
- [ ] **One aggregate endpoint.**
- List each kind with `readWorkspaceKind` (dynamic kinds) or
`typedKindScope` (typed kinds such as Pods), both in
`internal/server/kind_access.go`. Their answer's `coverage()` is a
`KindCoverage`: `full|partial|denied|notInstalled|syncing|uncached|error`,
with denied and uncached namespaces named only when the caller supplied the
namespace list.
- Radar's own cache scope comes from `namespacesWithinCache`
(`cache_scope.go`).
- A per-object read with several sources reports a `ReadSource`
`{state, grant, reason}` per source (`read_source.go`), with the missing
permission as a `Grant` (`internal/auth/grant.go`), never a sentence.
- Fan out over namespaces with `fanOut` (`fanout.go`), behind a cap.
- Prometheus series matched to an object by name rather than identity carry a
`SeriesIsolation` (`internal/prometheus/series_scope.go`).
- A GitOps or Helm manager comes from `topology.ManagedByFromMeta`, never from
labels read on the client.
- [ ] **Version skew.** Give the workspace's endpoints one `FeatureCapabilities`
flag and a `radarFeatures.ts` entry with `flagShippedWithEndpoint: true`.
- Gate every hook with `useRadarFeature`, including mutations, streams and
downloads. Never add `retry` to a mutation.
- When the Radar is too old, hide the sidebar destinations and fall back to
the standard detail views.
- [ ] **Findings.** The Issues engine comes first. A finding that depends on
the caller's grants (a measurement) stays in the workspace as a
`WorkspaceProblem` with `source: 'measurement'`, `measuredBy` and, when
matched by name only, `unverifiedMatch`. Add no new severity ladder, and
title reasons the Issues page already titles with `issueReasonTitle`.
- [ ] **Screens.**
- k8s-ui `components/facts`: `Fact`, `FactGrid`, `FactRow`, `FactValue`,
`FactSource`, `CertaintyGlyph` and `ManagedByText`. These are for any
surface that shows observed values, single-kind renderers included.
- k8s-ui `components/problems`: `WorkspaceProblem`, and
`ProblemCallout`/`ProblemList`/`ProblemMeta` with the workspace's
`rootKind`, plus `OpenIssueContext`.
- Also from k8s-ui: `SectionHeading`, `FoldSection` and `FoldSummary`
(`ui/FoldSection`), `ui/RefLink`, `toneTextClass`/`worseTone` in
`ui/status-tone`, and `formatGrant`.
- App: `web/src/components/workspace`:
- layout: `ScreenBody`, `ScreenEmptyState`, `Notice`
- controls: `Segments`, `FilterChips`
- tables: `SectionTable` and its table classes
- text: `RefreshFailedNotice` and `GrantText`
- Buttons are `.btn-brand` and `.btn-secondary`.
- [ ] **Detail page.** Through `WorkloadView`:
- `renderSummary`: a composed Overview; the resource's renderer moves to
"Spec & status".
- `extraTabs`
- `renderHeaderActions`
- [ ] **Actions.**
- Server (`internal/server/actions.go`):
- A capabilities endpoint answers each action as an `ActionCapability`
`{allowed, reason, permission, grant}`, built with `grantPermission` and
`capabilityVerdict`.
- The POST body is an `ActionRequest` `{reviewedContext, uid, facts, params}`
read with `decodeActionRequest`.
- Bind the facts the user reviewed. Refuse with 409 `changed`
(`changedAction`) or `context_changed`, and use `partialAction` when a
multi-step write stops part-way.
- Writes are impersonated, version-bound (`mergePatchAtVersion`) and never
retried.
- Client:
- `web/src/api/actions.ts`: `actionErrorCode`, widened with the integration's
own codes; `actionOutcomeLocked`, `actionCompleted` and `capabilityReason`.
- `ActionConfirmDialog` and the GitOps write guard (`useGitOpsWriteGuard`).
- An accepted POST is not a completed action: follow the outcome in status.
- [ ] **Docs and fixtures.** Add a `docs/<x>.md` listing which source each value
comes from and how it reads when unknown, a `scripts/<x>-demo.sh` with its
README, and a CLAUDE.md row.

These pieces are not shared yet, because they have one consumer and the second
should shape them:
- the App/route wiring
- the operation tracker
- the fixed-path `pods/proxy` reader
- the merged log stream
- the report bundle
- operator diagnosis

Read CloudNativePG's versions (`web/src/components/cnpg/`,
`internal/server/cnpg_*.go`), and extract one when you copy it.

## 4. Before submitting

- [ ] Verify status against the controller's documented API: desired versus
observed, unknown versus false, intentional pause/stop versus failure. Consider
Expand Down
5 changes: 5 additions & 0 deletions docs/STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ radar/
│ │ ├── shared/ # ResourceRendererDispatch, ResourceActionsBar, EditableYamlView
│ │ ├── gitops/ # Argo/Flux badges + actions + tree graph + insights views
│ │ ├── workload/ # WorkloadView
│ │ ├── facts/ # Observed values with their sources: facts, certainty glyph, GitOps manager
│ │ ├── problems/ # Problems with where their evidence came from (callout, list, origin)
│ │ ├── cnpg/ # CloudNativePG workspace model + composed summaries
│ │ ├── timeline/ # Timeline shared components
│ │ ├── logs/ # Log viewer core
│ │ └── ui/ # Shared primitives, package-owned Monaco/YAML runtime, Problems + review
Expand All @@ -115,6 +118,8 @@ radar/
│ │ │ ├── portforward/ # Port forward manager
│ │ │ ├── resource/ # Single resource detail page
│ │ │ ├── resources/ # Resource list panels (thin wrappers over @skyhook-io/k8s-ui)
│ │ │ ├── workspace/ # Workspace screen layout, tables and notices (Capacity, CloudNativePG)
│ │ │ ├── cnpg/ # CloudNativePG workspace screens, actions, runtime
│ │ │ ├── audit/ # Cluster audit detail view
│ │ │ ├── cost/ # Cost tracking and visualization
│ │ │ ├── settings/ # Settings dialog
Expand Down
2 changes: 1 addition & 1 deletion docs/capacity.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Operators rarely start at the nav. Capacity meets them where they are:

## Reading the numbers

Capacity's core contract is **per-value certainty**. Every quantity carries one of:
Capacity applies Radar's rules for unknown and partial values ([DESIGN.md](../DESIGN.md#unknown-partial-and-denied-values)) per quantity: its core contract is **per-value certainty**. Every quantity carries one of:

| Glyph | Meaning |
|-------|---------|
Expand Down
Loading