Skip to content

[Backport release-1.6] feat(dashboard): rework the Marketplace entry point and the Admin portal - #3952

Merged
Andrei Kvapil (kvaps) merged 9 commits into
release-1.6from
backport-3928-to-release-1.6
Aug 24, 2026
Merged

Andrei Kvapil (kvaps) merged 9 commits into
release-1.6from
backport-3928-to-release-1.6

Conversation

@github-actions

Copy link
Copy Markdown

Description

Backport of #3928 to release-1.6.

Open the grouped application list right away instead of the
"Choose a product to deploy" page and its Show-all-apps button.
Category links keep filtering to a single category.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit 5178e16)
Administration (Tenants, Modules, External IPs, per-tenant Info) leaves
the Console sidebar for Admin, which is now always visible: those pages
need no special permission. The two cluster-wide operator areas keep
their own gates, so Capacity and Backup Classes still appear only for
users who can open them.

Resource routes become portal-aware through a shared base-path helper,
so a detail, edit or order page opened from Admin stays under /admin
instead of jumping back to /console.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit ef73a0e)
The flat table becomes a tree built from TenantNamespaces, whose
`tenant.cozystack.io/<ancestor-ns>` labels carry the full ancestry. A
tenant whose parent is not accessible attaches to its nearest visible
ancestor instead of disappearing, and rows are labelled relative to that
parent, so a sub-tenant reads as `crpjxhwm` rather than
`whmcs-crpjxhwm`. Subtrees collapse from the row itself.

Each row also carries its own actions: Info opens that tenant's Info
page, and Create Tenant orders a sub-tenant at any level of the tree.
Editing a tenant switches to the namespace holding its Tenant CR — the
parent's, or its own for the hierarchy root.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit 7a48d45)
The Modules page listed the selected tenant's modules as Enabled or
Disabled cards, so a module a parent tenant provides looked disabled and
clicking it led to a Tenant CR that does not exist in the child's
namespace — an endless spinner.

It now covers every tenant the user can access, as the same hierarchy
tree the Tenants page draws, with each tenant's modules as chips
carrying the module icon, the namespace running it and its readiness.
Availability comes from the `namespace.cozystack.io/<module>` label on
each TenantNamespace, which names the providing namespace: the tenant
itself for a local module, an ancestor for an inherited one. An
inherited chip appears only when that ancestor is out of the user's
reach, since otherwise its own chip already shows the module. Clicking
any chip opens the module in whichever tenant runs it.

Tenants whose whole subtree has no modules collapse into a
"+ N tenants" branch, and the Info module is left out entirely as every
tenant carries it.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit 21b8a0e)
The page showed LoadBalancer services of the selected tenant only, so an
operator had to switch tenants one by one to find an address. It now
lists them for every visible TenantNamespace in a single table with a
Namespace column. A namespace whose services the user cannot list
contributes no rows instead of failing the page.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit 1279652)
Detail pages offered a fixed tab set, so Info showed Workloads, Services
and Ingresses tabs it never fills; tabs now appear only for resource
groups the instance actually owns. The probes reuse the tabs' own list
refs and label selector, so they share one cache entry and one watch.

Back and the sidebar followed the plural instead of the entry point: a
module page sent the user to a one-item list and lit nothing in the
sidebar. Module pages now return to Modules and keep it highlighted,
while Info returns to Tenants and highlights that, matching the trees
they are reached from.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit e755006)
Copying a secret key required revealing it on screen first, and the
button gave no sign that anything reached the clipboard. The value the
list already carries is enough to copy, so the button works while the
key stays masked, and it briefly turns into a check mark to confirm.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit cfd5444)
Opening the dashboard without a path sent the user to the catalog, and
the header gave Marketplace the primary emphasis. The Console is the
better entry point: it shows what the tenant already runs, which is
what someone opening the dashboard almost always came for. The
Marketplace stays one click away for when the intent is to add
something new.

The landing path becomes a named constant beside the portal helper, so
the redirect and the tab emphasis cannot drift apart, and the library's
own default tab set is reordered to match rather than contradict its
only consumer.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit bfd4ea0)
Four of the five findings share one root cause: a node in the tenant
tree can attach to its nearest *visible* ancestor when its real parent
is inaccessible, and per-row actions then derived their target from
that bridged ancestor.

The Edit action on a bridged tenant row pointed at a Tenant CR that
does not exist. The CR lives in the real parent's namespace, so the
target now comes from realParentNamespace — and Edit is offered only
when that namespace is readable, because otherwise there is no CR the
user could open and the button could only ever dead-end. The test that
asserted two Edit buttons had frozen the broken target as expected
behaviour; it now names the rows it expects, and the Edit button gained
a title so that assertion means something.

An inherited module chip is rendered only when the providing tenant is
NOT visible, yet it was a live button: clicking it denied the instance
GET and switched the active tenant to a namespace outside the visible
set, which the selector then bounced to an arbitrary fallback. It is
now an inert badge that states where the module comes from.

InfoRedirect's missing-definition branch hardcoded /console, throwing an
admin out of the portal its own docstring promises to preserve.

The sidebar's alsoMatch used a bare startsWith, so /admin/apps would
light up for /admin/apps-v2. It now matches whole path segments.

And SecretsTab let a rejected clipboard write escape as an unhandled
rejection, leaving the button silent — a denied permission or an
insecure context was indistinguishable from a no-op. It now reports the
failure.

Declined: CodeRabbit asked to split `import { useK8sList, type K8sResource }`
into a separate `import type`. The inline modifier is valid under
verbatimModuleSyntax and is what 26 other imports in this console use,
so the change would make the file the odd one out.

Reported-by: Ivan Okhotnikov <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
(cherry picked from commit 724354f)
@kvaps
Andrei Kvapil (kvaps) merged commit 2d404ca into release-1.6 Aug 24, 2026
2 checks passed
@kvaps
Andrei Kvapil (kvaps) deleted the backport-3928-to-release-1.6 branch August 24, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant