UI prototype and design notes for rancher/fleet 5711, which covers 4843 and 4844. Built from f3dcae8ae on 2026-11-04, on top of dashboard master, proxying the mf Rancher.
fleet-default. The page is at Continuous Delivery, Resources, OCI Storage.edge-store-0203.apac-clusters, set Replication wait to 2. Effect on Clusters now predicts Failover. Save it and check the Clusters tab.eu-staging-01 and any registry. The form warns that eu-clusters matches it first. Save, then move it up with the arrows.fleet-local and add one registry.Fleet stores a bundle in one registry and every target cluster pulls from that same place. That breaks when the management cluster and downstream clusters can't use the same registry, namespace or credentials, or when a site needs failover. The RFEs ask for per-cluster registry, credentials and namespace (4844) and for multiple registries with failover (4843). The triage comment on 4843 sets two constraints: mirroring stays out of Fleet's scope, and agents must retry with back-off when an artifact hasn't replicated yet.
What Fleet does today, which shaped the design:
fleet apply pushes to the secret named in ociRegistrySecret, or to the ocistorage fallback, and writes a copy of those settings to an internal secret named after the artifact (apply.go). With neither secret, the bundle stays in etcd (apply.go).bundle_controller.go). That copy is why changing a secret has no effect until the next update. It is also where per-cluster credentials would have to be picked, as the triage comment suggests.agentUsername if set, otherwise with the push credentials (ociwrapper.go). It then checks that the content matches the artifact ID (deployer.go). That check is what makes failover between registries safe.ociRegistrySecret only exists on GitRepo. HelmOps pull their chart directly, so the HelmOp form isn't touched.| Who | Needs to | In the prototype |
|---|---|---|
| Platform admin | Turn OCI storage on with one registry, like today | Add Registry. The first one becomes the source and the default route. No routing to configure. |
| Platform admin | Send each region to its own registry, with its own namespace and credentials | Registries, then Cluster Routing. A route picks clusters by name, cluster group or label. |
| Platform admin | Keep deploying when a regional mirror is down | A route's Pull Order. The cluster shows Failover and names what failed. |
| Edge admin | Serve air-gapped sites whose registry the management cluster can't reach | Route by cluster group, and Test Connection from a cluster instead of the controller. |
| App owner | Deploy without knowing the topology, or pin one workload to a registry | GitRepo form, Advanced step: follow workspace routing (default) or pin. |
| On-call | "Why is this bundle stuck on this cluster?" | Clusters tab and its drawer, or the OCI Storage tab on the Fleet cluster page: state, reason, next step. |
| On-call | "Did the last push reach everywhere yet?" | Artifacts tab: per registry and per cluster. |
| Anyone changing routing | "What happens if I save this?" | Effect on Clusters in the route form: chain before and after, expected state, routes that shadow this one. |
| Security review | Which clusters get which credentials? Is TLS off anywhere? | Agent Access column, and warnings when agents would get push credentials or TLS is off. |








ociRegistrySecret means today.

Exists Fleet has it today, though the dashboard reads neither contentsId nor ociReference yet. New needs a backend change. Derived the UI can compute it once the new data exists.
| Data | Why a user wants it | Status | Source | Priority |
|---|---|---|---|---|
| Registry | ||||
| Host and namespace | Where artifacts live. Customers think of these separately, so the form splits them. | Exists | Secret reference | Must |
| Role: source or mirror | Fleet needs to know where it may push. Users need to know what they must replicate. | New | New field or label | Must |
| Push and agent usernames, TLS mode | Spot agents that hold push credentials, and registries with TLS off. | Exists | Secret keys. Needs Secret read access, see ask 2 | Should |
| Clusters routed here, clusters pulling now | Is this registry used? How much breaks if it goes down? | Derived | Routes plus agent status | Should |
| State and failing clusters | Tells "registry down" apart from "one cluster has a network problem". | Derived | Agent status | Should |
| Last push, last pull | Quick signal that it is alive. | New | Bundle and agent status | Nice |
| Artifact count and size | Capacity planning. Size is why people use OCI storage at all. | New | Bundle status | Nice |
| CA expiry | Warn before a bundle silently stops being pullable. | Derived | Parse cacerts | Nice |
| Reachability from a given cluster | The controller often can't reach a mirror, so testing from it proves little. | New | Agent-side probe | Nice |
| Route | ||||
| Cluster selection, ordered registries, replication wait | The mapping both RFEs ask for. | New | Routing resource | Must |
| Matching clusters | Preview before saving, and see which route wins. | Derived | UI can compute from labels and groups | Must |
| Cluster | ||||
| Effective route and registry chain | The direct answer to "which registry, which credentials, which namespace". | New | Cluster.status | Must |
| Registry in use now | Primary or failover? | New | Agent-reported | Must |
| Result per registry, with a failure class | Auth, TLS, unreachable, not found and integrity need different fixes. | New | Agent-reported | Must |
| Last successful pull | How stale is this cluster? | New | Agent-reported | Should |
| Config in use vs config wanted | Shows routing changes that haven't landed. See question 1, this may not be needed. | New | Generation or hash | Should |
| Bundle and artifact | ||||
| Artifact ID | What to look for in the registry. | Exists | Bundle.spec.contentsId | Should |
| Reference | Today it is a single value marked informational. It needs to be per cluster. | Exists | Bundle.status.ociReference | Should |
| Per-cluster pull state, attempts, next retry | Tells a slow mirror apart from a broken one. Without it this reads as an error. | New | BundleDeployment.status | Must |
| Availability per registry | Only known from agents: Fleet can't see inside a mirror it may not reach. | Derived | Agent status | Should |
| Size, pushed at | Replication lag needs a start time. Size explains slow pulls. | New | Bundle status | Should |
| Pull duration | Finds a mirror that works but is slow. | New | Agent-reported | Nice |
Cluster.status as well: the Fleet cluster page already loads every BundleDeployment, and that doesn't scale to a table of thousands of clusters.WaitingForDependency, and the dashboard needed fixes because it painted the new state red. Emit it in status.display.state and in the summary counts from day one, and we add it to the UI state tables alongside.The field names below are only there to make the idea concrete:
apiVersion: fleet.cattle.io/v1alpha1
kind: OCIStorageRouting # hypothetical name
metadata:
name: default
namespace: fleet-default
spec:
routes: # first match wins
- name: eu-clusters
clusterSelectors: # same vocabulary as GitRepo targets
- matchLabels: { region: eu }
registries: [harbor-eu, harbor-central] # existing OCI storage secrets
replicationTimeout: 10m
- name: air-gapped-stores
clusterGroups: [airgap-stores]
registries: [zot-stores]
default:
registries: [harbor-central]
# BundleDeployment.status, reported by the agent (hypothetical) ociPull: registry: harbor-apac state: WaitingForArtifact # Pulled | WaitingForArtifact | Failed reason: NotFound # Unauthorized | TLS | Unreachable | NotFound | IntegrityMismatch attempts: 3 nextRetry: "2026-11-04T16:21:00Z" lastSuccess: "2026-11-04T10:12:00Z" # Fleet Cluster.status, aggregated so the UI doesn't have to load every BundleDeployment ociStorage: route: apac-clusters chain: [harbor-apac, harbor-central] active: harbor-apac waiting: 1 failed: 0
The first three are from the issue. The UI only depends on "registries, routes, status", so changing this later only touches the prototype's data layer.
| Option | Good | Problem for users |
|---|---|---|
Field on the Fleet Cluster | Easy to explain. Per-cluster view is trivial. | One edit per cluster. A new cluster uses the fallback registry until someone sets it, and its bundles fail if it can't reach that one. A failover chain needs a list anyway. |
| Target customizations | Per-app flexibility. | App owners need to know the registry topology. The same mapping is copied into every repo. No single place shows where a cluster pulls from. |
New secret structure, or a separate ocistorageForClusters secret | No new CRD. Same idea as helmSecretNameForPaths. | Routing sits inside a Secret, so seeing it requires Secret read access, which a read-only user or on-call engineer may not have. No status to report on, no field validation, and the UI has to parse and rewrite a blob. |
| New routing resource that references today's secrets (what the prototype assumes) | Credentials stay in Secrets. Routing is readable with normal RBAC, can be validated, and has its own status. Existing secrets keep working. | A new CRD to own and migrate to. |
My recommendation from the UI side is the new resource. The secret option can work if a new CRD is a hard no, but then status on Cluster and BundleDeployment matters even more, because the routing itself won't be readable for many users. A per-cluster override doesn't need its own field: it's a route that names one cluster (lab-sandbox in the sample data).
For the Fleet team
apply.go). Mirrors only lose them if the replication tool propagates deletes.For UX