The proxy can be left out of an installation, and then the console
nginx answered the observability roots with its own 502. The console
read that as an unreachable store: three errors, a Retry button and a
live tail reconnecting forever, instead of a notice that the feature
is not available on this cluster.
nginx now turns the 502 and 504 it generates itself, when it cannot
resolve or reach the proxy, into a 404 Kubernetes Status, which the
console already treats as not installed. Upstream errors are not
intercepted, so the proxy's own 502 and 504 for a store still reach
the console unchanged. The bats test runs the extracted locations in a
real nginx when one is installed.
Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
The logs and metrics roots under /apis/cozystack.io and
/apis/apps.cozystack.io are served by the observability proxy, not by
the API server, so the console nginx sends them there ahead of its
/apis prefix location, unbuffered and with the same long timeouts, so
that a live tail reaches the browser as it is written.
nginx resolves a literal upstream once at startup and refuses to start
when the name does not resolve, which would take the console down on
every cluster that does not run the proxy. The upstream is therefore a
variable resolved per request, and such a cluster answers 502 on those
paths only. That resolver does not apply the pod's search domains, so
the config becomes a template the image's entrypoint renders with the
pod's nameservers and the platform cluster domain, which the chart now
passes to the console.
Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
The tenant list rendered the name as plain text and put the row's only
link on an Edit button, so nothing in the list reached the detail page
that every other kind gets, tabs and a Delete action included. The only
way there was to open the edit form and cancel out of it.
The name cell becomes a link wherever Edit is offered, to the same CR.
The detail page reads the namespace from the tenant context, so a plain
click aligns the context to the row's parent first, the way Edit
already does. React runs that handler for a ctrl-, cmd- or shift-click
too, which the browser turns into a new tab or window, so it runs only
for the click that navigates its own tab; otherwise opening a tenant
beside the current one would move the tab left behind as well.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
A tenant's CR lives in its parent's namespace, and the console recovered
the parent from the namespace name: `tenant-a-b` sits under `tenant-a`.
The root is the one level that naming skips. A tenant `acme` ordered in
`tenant-root` owns `tenant-acme`, not `tenant-root-acme`, so the prefix
test never matched a top-level tenant and `realParentNamespace` returned
undefined for it. That is most tenants on a normal cluster, and the Edit
button, offered only where the CR's namespace resolves, was missing for
all of them.
The ancestor labels carry the parent the name does not, so the lookup
falls back to `tenant-root` when the chain names it. Deriving the CR name
keys on the parent being the root rather than on the child's name
happening to start with it, which keeps a root-level tenant called
`root-x` from resolving to the CR `x`, and the row label agrees with it
rather than showing `x`.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Removing on-connect auto-registration made several user-facing and doc texts
wrong: they promised that tapping registers a repository's apps in the catalog
and runs its charts. Tapping now installs nothing, so the texts told operators to
wait for apps that never appear.
Reword the `cozypkg tap`/`untap` help, the tap success output (now printing a
`cozypkg add <name>` line per materialized PackageSource), the `cozypkg init`
scaffold README, the packages-index trust model, the dashboard Repositories
banner and per-repository empty state, and the stale re-registration code
comments. No behaviour changes beyond the added `cozypkg add` hint in the tap
output.
Assisted-by: LLM
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
## What this PR does
The marketplace and console sidebars render a fixed category list,
`["IaaS", "PaaS", "NaaS"]`, and drop every other
`spec.dashboard.category`. A repository tapped with `cozypkg` that
brings its own category is reachable only at the bottom of the full
application list, and its installed applications get no console section
at all.
Both sidebars now follow `groupByCategory`, the order the marketplace
page already uses: IaaS, PaaS, NaaS first, then any other category
alphabetically. `Administration` stays in the Admin portal, as before.
Unknown categories get the generic grid icon.
The note in `docs/agents/contributing.md` that described the hardcoded
list is removed, since it no longer holds.
Tests: two new cases in `sidebar-sections.test.tsx` (a tapped
`Paleocomputing` category appears after the built-in ones in both
sidebars, `Administration` stays out). Both fail on the previous code.
The console suite (448 tests), `pnpm typecheck` and `eslint` on the
changed files pass.
### Screenshots
Same cluster and tenant, a repository tapped that brings the
`Paleocomputing` category.
Before, marketplace: the category is only reachable at the bottom of the
full list.

After, marketplace: the category gets its own filter.

Before, console: no section for the tapped applications.

After, console: installed applications of that category get their own
section.

### Downstream repositories
Walked the trigger map against the diff: it touches only the console
sidebar code and a note in `docs/agents/contributing.md`. No package,
values schema, CRD, installer value or `ApplicationDefinition` field
changes. The website page on `ApplicationDefinition` does not restrict
`spec.dashboard.category` values, and `external-apps-example` uses
`PaaS`, which keeps working as before.
- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:
### Release note
```release-note
feat(dashboard): categories brought by tapped repositories now appear in the marketplace and console sidebars, after IaaS, PaaS and NaaS, instead of only in the full application list.
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Marketplace and Console sidebars now show application categories
beyond the built-in options, including custom categories. Categories
appear in the order provided by the application grouping.
* The Administration category remains excluded from both sidebars.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What this PR does
Six small Console fixes from the #3828 triage. Each one is small and
most of the code sits in `apps/console/src`, so they go together in one
PR.
`Fixes #3105`: sizes between 1Ki and 1Mi printed as a raw byte count.
`humanizeBytes` had branches for Ti, Gi and Mi and then fell through to
bytes. It now has a Ki branch with the same `toFixed(0)` as the Mi
branch. The `1023B` case still holds, and the test covers 1Ki and 512Ki.
`Fixes #3106`: the tenant picker showed on admin pages where the tenant
changes nothing. It is now hidden on the cluster-scoped admin pages
only: Capacity, Backup classes, External IPs and Modules. The rest of
`/admin` mounts the same tenant-scoped pages as `/console`, and those
take their namespace from the tenant context, so the picker stays there.
`/admin/tenants` keeps it too, because its rows come from the
picker-filtered tenant list.
`Fixes #3107`: both capacity drill-downs showed one generic error, so a
permission failure looked like a broken page. Both now use the `error
instanceof K8sApiError && error.status === 403` check that
`ClusterStorageSection` already uses, with a test for the forbidden case
and one for a generic error on each page.
`Fixes #3102`: an immutable leaf was not restored when the YAML edit
dropped its parent object. `overlayPath` stopped as soon as the
submitted body had nothing at a path segment. It now creates `{}` for
the missing parent, but only when the target there is undefined or null
and the original really has the leaf to restore. So a scalar or an
explicit null the user put there survives. A wildcard with more path
under it does not rebuild a deleted parent, since that would put back
entries the user removed. The test uses foundationdb's
`storage.storageClass`, which is a shipped path of this shape.
`Fixes #3135`: a blocked submit did not scroll to the field that blocked
it. Plain `focusOnFirstError` crashes here, because RJSF's built-in
handler reads `form.elements` and this form is `tagName="div"`. So the
form passes a small handler that finds the field by its generated id and
scrolls it into view. The ids come from one shared definition, and the
widgets that had none now carry it: the `DynamicOptionsWidget` select,
which every `x-cozystack-options` field uses, and the `SourceField`
controls. The nested form that `AdditionalPropertiesField` mounts per
map entry continues the outer id namespace instead of restarting at
`root`, so fields like `nodeGroups.*.instanceType` resolve. A map key
with a dot in it, such as a mariadb user `john.doe`, resolves by trying
both readings of the dotted error path.
`Fixes #3108`: `cilium`, `coredns` and `verticalPodAutoscaler` have only
`valuesOverride` and no `enabled`, so they rendered as plain groups next
to the toggleable addons and looked like a switch that failed to appear.
Setting `<addon>.enabled: true` in the YAML editor is accepted, because
the schema sets no `additionalProperties`, and the API stores the field
and echoes it back. Nothing reads it. The three addons are not the same
case. `cilium` and `coredns` are always installed.
`verticalPodAutoscaler` has no switch of its own and is installed and
removed together with `addons.monitoringAgents.enabled`. The three are
now marked in `values.yaml` with a new `x-cozystack-no-enable-switch`
schema keyword, and the form renders them with a note that there is no
enable switch and an `enabled` field in YAML does nothing. The per-addon
reason goes into the schema description, which the form renders and
which regenerates into the README, the Go types and the
ApplicationDefinition.
The generated files are regenerated with cozyvalues-gen v1.7.0, the
version CI pins, and the pre-commit check passes. The console typecheck
and tests pass in CI.
### Screenshots
##### Ki formatting
<img width="1182" height="184" alt="image"
src="https://github.com/user-attachments/assets/62662265-4f77-455d-b90e-85e417684bb9"
/>
##### Admin without tenant picker
<img width="1440" height="245" alt="image"
src="https://github.com/user-attachments/assets/99aff4f3-fda9-4c4c-8cf8-0c1eb01fb973"
/>
##### Capacity 403 error
<img width="1232" height="270" alt="image"
src="https://github.com/user-attachments/assets/f4e4b328-ee5e-4516-b78f-9b5084b6bbc8"
/>
<img width="1232" height="944" alt="image"
src="https://github.com/user-attachments/assets/142e438e-f538-436e-aae8-dfb79ec747e8"
/>
##### Validation focus
<img width="1166" height="135" alt="image"
src="https://github.com/user-attachments/assets/89abb73b-a624-406f-9736-3bf38bb8a923"
/>
##### Addon lifecycle
<img width="1166" height="591" alt="image"
src="https://github.com/user-attachments/assets/14a95891-84d1-4073-a009-bc0eab94f114"
/>
### Downstream repositories
I walked the trigger map in `docs/agents/contributing.md` against the
diff, file by file. This PR changes field descriptions, adds the
`x-cozystack-no-enable-switch` schema keyword on three addon objects,
and changes the console. No field is added, removed or renamed, and no
default, version enum, `kind`, `plural`, `release.prefix`, output Secret
or Service changes, so none of the terraform provider triggers fire. No
package is added or removed, and nothing changes in
`packages/core/platform/values.yaml`, the variants, `hack/` or the node
prerequisites. cozyvalues-gen itself is unchanged, since the keyword
goes through its generic `@x-*` passthrough. The kubernetes reference
page on the website regenerates from `README.md` on a stable tag, which
is automated rather than a follow-up.
- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:
### Release note
```release-note
fix(dashboard): Ki-range sizes now render as Ki instead of raw bytes, the tenant picker is hidden on cluster-scoped admin pages, capacity drill-downs distinguish a permission error from a broken page, an immutable field whose parent object is absent is now applied, a blocked submit scrolls to the field that blocked it including fields inside maps and the source and dropdown widgets, and the cluster addons with no enable switch now say so instead of showing a toggle that never appears
```
A JSON object that parses but fails the schema, for example on
minProperties, left the textarea with aria-invalid false, and nothing
linked it to the error list RJSF renders under the field, so assistive
technology announced the field as valid. The field now reports the
schema errors RJSF passes in and points aria-describedby at their list,
unless hideError keeps that list from rendering. The red help text
stays tied to input that is not a JSON object.
Assisted-by: LLM
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
## What this PR does
Follow-up to #4612.
The ConfigMaps tab takes its names from the application's resource map.
React Query keeps the last successful list when a refetch fails, so when
the resource map became forbidden or went away after a successful read,
`useApplicationConfigMaps` went on parsing the old copy. The tab stayed
with the old names, every listed ConfigMap showed an access error, and a
malformed map kept its old error. A map that is unreadable from the
start already hid the tab.
Now a 403 or 404 on the resource map drops the names and the error
whether or not an earlier read succeeded, so the tab goes away in both
cases. Any other error is still shown on the tab, as before.
The new tests are a page test with stale resource map data next to a
403, and hook tests that read the map and then refetch it with 403 and
with 404, plus a malformed map followed by a 403. All four fail on main
and pass here. `pnpm typecheck`, `pnpm test` (451 tests in 62 files) and
`pnpm lint` pass.
### Screenshots
No visual change. Once the resource map is unreadable, the page looks
the same as for an application whose map was never readable, with no
ConfigMaps tab. The tab itself is shown in #4612.
### Downstream repositories
Walked the trigger map against the diff. It touches only the console
hook that reads the resource map and its tests. No chart, values,
schema, CRD or `hack/` file changes, and the console is vendored in this
repository.
- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:
### Release note
```release-note
fix(dashboard): the ConfigMaps tab now goes away when the application's resource map stops being readable, instead of keeping the names from the last successful read
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* ConfigMaps are no longer displayed when access to the resource map is
forbidden or the resource map is missing.
* The ConfigMaps tab is excluded when the resource map cannot be
accessed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What this PR does
When the dashboard session expires, the console now sends the user back
to sign-in instead of sitting on a stale page. This re-applies
cozystack/cozystack-ui#34 by @IvanHunters onto the in-tree console,
since the UI was vendored into the monorepo (#2963) and the standalone
repo is being archived. It is part of the cozystack-ui retirement
tracked in #3097.
When the `kc-access` cookie expires, k8s API calls return 401.
`K8sClient` now treats a 401 as an auth boundary and makes one top-level
navigation to `/oauth2/sign_in?rd=<current-url>`. That path is the
sign-in entry both gatekeeper modes serve. With OIDC enabled,
oauth2-proxy goes straight to the provider (`--skip-provider-button`)
and returns the user to `rd`. Without OIDC, token-proxy renders its
token form there, but it does not honour `rd` yet, so the user lands on
`/` after sign-in.
The handler can be replaced through `K8sClientConfig.onUnauthorized` and
does nothing outside the browser. A latch keeps concurrent 401s from
redirecting more than once, and any 2xx from either fetch site re-arms
it, so a handler that re-authenticates in place still sees the next
expiry.
Related: #2788.
### Verification
`client.unauthorized.test.ts` covers the hook on the request and watch
paths, the latch across concurrent 401s, the re-arm after a success, and
the default handler's target and relative `rd`. `pnpm typecheck` and the
console test suite pass.
Original PR: cozystack/cozystack-ui#34, credited via `Co-authored-by`.
### Screenshots
Not a UI change.
### Downstream repositories
- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:
### Release note
```release-note
fix(dashboard): on a 401 (expired session) the console now sends the user to sign in again instead of silently showing a stale page. With OIDC enabled the user returns to the page they were on; without OIDC they land on the start page after entering a token.
```
React Query keeps the last successful list when a refetch fails. After the
resource map became forbidden or went away, the hook went on parsing that
old copy, so the ConfigMaps tab stayed with stale names, and a malformed
map kept its old error, instead of the tab going away as it does when the
map is unreadable from the start.
Assisted-by: LLM
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
unauthorizedHandled was set once per client and never cleared, so it
collapsed every later 401 too, not just the concurrent ones it was meant
to. Harmless for the shipped default handler, which navigates away and
takes the client with it, but the onUnauthorized hook exists precisely so
a caller can re-auth in place, and such a handler would fire exactly once
for the client's lifetime and then go silent.
Clear the latch on any 2xx from either fetch site: a success means the
session is alive again, so the next 401 is a new episode. The storm guard
still holds, since concurrent 401s have no success between them.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The dashboard gatekeeper runs one of two mutually exclusive proxies,
chosen by the cluster's oidc-enabled flag (templates/gatekeeper.yaml).
/oauth2/start exists only in oauth2-proxy. The token-proxy branch
registers only sign_in, sign_out and userinfo under the /oauth2
prefix, so there /oauth2/start falls through to the catch-all
reverse-proxy handler, which does not re-verify the token. With a
present but expired cookie it proxies the request to the console
upstream, the SPA reloads, gets a 401 again and loops.
/oauth2/sign_in is the one entry point both modes implement, so the
default 401 handler goes there. oauth2-proxy's sign_in hands straight
off to OAuthStart because the gatekeeper passes --skip-provider-button,
and it resolves rd through the same app-director, so the OIDC path
behaves as it does through /oauth2/start. token-proxy serves its token
form there. It does not honour rd yet, so after sign-in the user lands
on / instead of looping.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The 401 handling is what brings an expired session back to sign-in,
and a regression in it stays invisible until a cookie expires. The
test drives K8sClient through the vi.stubGlobal("fetch", ...) harness
the sibling k8s-client tests use. It checks that the hook fires once
per 401 from both the request and the watch fetch sites, once across
concurrent 401s, never on other responses, and that the default
handler passes the current location as a relative rd, which
oauth2-proxy's open-redirect check accepts.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
When the kc-access cookie expires, k8s API calls return 401 and the
SPA stays on a stale page until the user clears the cache by hand.
K8sClient now treats 401 as an auth boundary and makes one top-level
navigation into the oauth2-proxy sign-in flow, passing the current
location as rd so the user comes back to the same page. The handler
can be replaced through K8sClientConfig.onUnauthorized and does
nothing outside the browser. The unauthorizedHandled latch keeps
concurrent 401s from redirecting more than once.
Re-applied from cozystack/cozystack-ui#34 onto the in-tree console
after the UI was vendored (#2963), since the source repo is being
archived. Related: #2788.
Co-authored-by: IvanHunters <49371933+IvanHunters@users.noreply.github.com>
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Continuing the id namespace into a map entry's nested form put the
literal key into the DOM id, which made a pre-existing gap reachable
with a shipped schema: a mariadb user named john.doe renders
root_users_john.doe_maxUserConnections, while the error path
.users.john.doe.maxUserConnections split into one segment too many and
resolved nothing. The flattened path cannot say whether a dot separates
two properties or sits inside one name, so try the plain split first and
then each way of gluing adjacent segments back together, bounded, taking
the first id that exists. Only exact ids are tried this way; the
descendant fallback stays on the plain split, where it is meaningful.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
tenant list
The focus helper named SourceField as the widget it existed for and its
test pinned `.source`, but vm-disk marks source.http.url, source.disk.name
and source.image.name required and source itself not, so `.source` is a
property RJSF cannot report. What it does report is one level deeper,
where SourceField's controls carried neither id nor name, so choosing
the http source, leaving the URL empty and pressing Deploy blocked the
submit and then did nothing at all. Give those controls the generated
id, label them with it, and test the shape the shipped schema actually
produces.
/admin/tenants is not cluster-scoped: its rows come from the
picker-filtered context list, unlike Modules and External IPs which list
tenant namespaces themselves. Hiding the picker there stranded the tree
rooted at whichever child the user had drilled into, with no control to
widen it, and the unanchored prefix took the row detail and edit routes
with it. Drop it from the list and anchor the match on a segment
boundary.
The picker now carries a test marker on every branch, because asserting
on its label could not distinguish "not rendered" from "still loading"
and collided with the tenant page's own Tenant button -- which is why
that page went untested while both defects sat in it.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
picker where the tenant still matters
AdditionalPropertiesField mounts a nested form per map entry, and that
form was given no id configuration, so it restarted at "root": an entry
field rendered as root_<field> while its validation error named
<outer>.<key>.<field>, and the focus helper resolved nothing. That is
the case #3135 names -- nodeGroups.*.instanceType is inside such a map
-- along with nine other required fields across computeplane, mariadb,
rabbitmq and seaweedfs. Continue the outer namespace into the nested
form, from one shared definition so the generated ids and the ids the
helper rebuilds cannot drift apart.
Hiding the tenant picker keyed on the whole /admin prefix, but /admin
also mounts the same tenant-scoped resource pages as /console and they
take their namespace from the tenant context. Reaching one of those
from Modules switches the active tenant on the way, so the tenant was
left invisible and unchangeable on the pages it governs. Key on the
cluster-scoped areas the issue actually names.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Deleting scrollIntoView left every focus test green, though it is the
only thing that brings the field into view: focus is called with
preventScroll, which turns off the browser's own scroll-on-focus. So
the behaviour the fix exists for was unpinned. Stub scrollIntoView,
which jsdom does not implement, and assert it. Same for the selector
escaping, which only the fallback path reaches, so the test has to miss
the exact id for an unescaped quote to break anything.
Keep the test QueryClient at retry and gcTime only. No test needs the
wider production defaults: the suite is green either way,
while staleTime and placeholderData change what all 53 files observe.
It belongs with the change that needs it.
Record the new schema keyword in the console notes.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
DynamicOptionsWidget never took `id` off its props, so its select
carried none. Every x-cozystack-options field goes through it --
storageClass, backupClass, VM disks, GPU names -- so a blocked submit
on any of them resolved no element and scrolled nowhere, which is the
whole point of the focus helper.
Put the id on the select, and widen the helper's descendant fallback
from inputs to any form control, so a widget that still omits its id
degrades to its nearest labelled child instead of nothing.
The existing focus tests build their DOM by hand and so could not see
this: removing the id again fails the widget's own test instead.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The test asserted that the schema description renders and that neither
piece of removed copy does, but the default renderer prints the
description too and has never carried that copy, so the assertions held
whether or not the no-toggle branch was taken: forcing hasNoToggle
false left this one green while its three siblings failed.
Assert the branch's own notice alongside the description.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The two bracket-path tests survived deleting the bracket parser they
claimed to cover: with no sibling sharing the prefix, the descendant
fallback happened to land on the only input present, so both passed.
The parser was unreachable anyway -- RJSF builds `property` by swapping
the instance path's slashes for dots and never emits brackets -- so
drop it and test the dotted shape that actually arrives, with a decoy
sibling so the assertion cannot pass on the fallback alone.
Pin the gap the dotted form carries: a property whose own name contains
a dot, which an additionalProperties map keyed `ghcr.io` produces, is
indistinguishable from one more level of nesting, so its field is not
focused. The submit still blocks and the inline error still renders.
Also give the test QueryClient the production defaults that change what
a component observes. `placeholderData` serves the previous query's
data with status "success" on a key change, so without it no test can
see the window where the data is stale but no longer loading.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The leaf check recursed through a wildcard, so an ancestor the user had
deleted was rebuilt for a per-element immutable path -- putting back the
entries they removed, which is the opposite of what the granular overlay
does everywhere else. Only a trailing wildcard, which freezes the whole
collection, justifies rebuilding it. Pin both answers; no shipped schema
nests a wildcard under an immutable path yet.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The admin assertion looked for "No tenants found", which the picker only
renders once an empty tenant list has arrived. With the list still in
flight the picker says "Loading tenants…" instead, so the assertion
passed whether or not the picker was there -- restoring the
unconditional subtitle left the suite green.
Give the provider a tenant list, wait out the loading branch, and match
the picker's own label on a route that has no "Tenant" text of its own,
plus the other direction: the picker still renders outside admin.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The guard keyed on the intermediate ancestor being present in the
original rather than on the immutable leaf being restorable, so an
explicit null the user submitted at that ancestor was traded for an
empty object with nothing put back into it. Confirm the leaf over the
remaining path first, wildcards included.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
When no element carries the generated field id, the fallback matched any
input whose id or name merely started with it, so an error on `data`
focused the sibling `database` and scrolled to a field the user was not
asked about. Match on a segment boundary instead: the separator for a
nested input, the dash for the SourceField radios named `<id>-source`,
which are the only names any widget emits.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Use the shared RJSF ID configuration in the focus helper, preserve
quoted dotted property names, and restrict its fallback to form inputs.
Add route and scalar-ancestor regression coverage for the rebased admin
hierarchy and immutable overlay.
Mark Kubernetes addons without their own switch explicitly in the
generated schema instead of inferring the behavior from their current
fields, and keep lifecycle-specific copy in each field description.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
cilium, coredns and verticalPodAutoscaler carry only valuesOverride and
no enabled, and the addon template keys its toggle off the presence of
enabled, so all three rendered as plain groups among the toggleable
addons, reading as a switch that failed to appear. That is what leads
users to add <addon>.enabled: true in the YAML editor, where the schema
sets no additionalProperties, so the API stores the field and echoes it
back while nothing reads it.
The three are not the same case, so the form must not claim they are.
cilium and coredns are always installed. verticalPodAutoscaler has no
switch of its own but is installed and removed together with
addons.monitoringAgents.enabled, so calling it mandatory would be
wrong. The fixed copy states only what holds for all three, that there
is no enable switch and that an enabled field in YAML does nothing, and
the per-addon reason moves into the schema description, which the form
already renders and which regenerates into the README, the Go types and
the ApplicationDefinition.
Also types the test fixture instead of spreading it as object, which
erased the props and left the file failing tsc.
Fixes#3108
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The app form validates on submit with the error list hidden, so a
required field left empty made Save look like a no-op: the error
rendered somewhere off screen. Pass focusOnFirstError. RJSF's built-in
handler resolves the field through form.elements, which the
tagName="div" form does not have, so resolve it by generated id
instead.
Fixes#3135
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
overlayImmutable stopped walking as soon as the submitted body had
nothing at a path segment, so a YAML edit that dropped a whole parent
object also dropped the immutable leaf under it -- reachable through
foundationdb storage.storageClass and kafka kafka.storageClass. Create
the missing ancestor when the persisted spec has one, and turn the
pinned FIXME test into a test of the fixed behaviour.
Fixes#3102
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
ClusterUsageResourcePage and StorageClassUsagePage rendered the same
"Failed to load..." text for every list error, so a user who can list
nodes but not pods or PVCs sees what looks like a broken page. Check
K8sApiError.status the way ClusterStorageSection already does in the
same Capacity area.
Fixes#3107
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The Breadcrumb subtitle is a tenant picker, and it rendered on every
route including the cluster-wide /admin Capacity views, where picking
a tenant changes nothing. Reuse the inAdmin flag that already selects
the sidebar sections to drop the subtitle there.
Fixes#3106
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
humanizeBytes branched on Ti/Gi/Mi and then fell through to a raw
byte count, so every value between 1KiB and 1MiB printed as e.g.
"524288B" instead of "512Ki". Add the missing Ki branch, formatted
without decimals like the Mi branch above it.
Fixes#3105
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Private infrastructure names, a real hostname, a commented-out
internal mirror block, an internal product name, and a reviewer's
name in a comment do not belong in a public repository; drop them
and replace what needs a stand-in with neutral placeholders.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
<!-- Thank you for making a contribution! Here are some tips for you:
- Use Conventional Commits for the PR title: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scopes are not an exhaustive list — pick the most specific scope for
the change and extend the list when a genuinely new area appears.
Examples:
- System components: dashboard, platform, operator, cilium, kube-ovn,
linstor, fluxcd, cluster-api
- Managed apps: postgres, mariadb, redis, kafka, clickhouse,
virtual-machine, kubernetes
- Development and maintenance: api, hack, tests, ci, docs, maintenance
- Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or
add a `BREAKING CHANGE:` footer
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `kind/backport` if it's a bugfix that needs to be
backported to a previous version.
-->
## What this PR does
The VM image picker no longer offers golden images whose import failed.
A golden keeps its PVC in `cozy-public` after the import fails, so a
picker built on the PVC list offered an image whose clone never
completes.
The `image` option source in cozystack-api now reads the
`vm-default-images-*` DataVolumes next to the PVCs and drops names whose
DataVolume is in phase `Failed`. I filter rather than mark them, because
such an option can never be used and `OptionItem` has no disabled state.
A golden that is still importing stays in the list, since CDI holds a
clone until the source is populated, and so does a PVC with no
DataVolume. When the DataVolumes cannot be read, the PVCs go out
unfiltered. The Option list drops a source whose provider errors, and an
empty picker would be worse than the old behaviour. cozystack-api gets a
read grant on `cdi.kubevirt.io` DataVolumes for this.
The console did not use that source at all. `SourceField` listed the
PVCs itself, as the tenant, so the fix above would not reach the picker
users see. It now reads the same Option list the other dynamic dropdowns
use. With that, nothing in the console lists `cozy-public` PVCs, so I
removed the read grant on them from the `cozy:tenant:dashboard` Role.
That is a visible change: a tenant loses `kubectl get pvc --namespace
cozy-public`, which was only ever granted for this picker in #2843.
One limitation: in CDI v1.66.1, which cozystack ships, a golden import
reaches `Failed` only on a checksum mismatch, and a broken URL keeps it
in `ImportInProgress`. So this filter covers less than the issue
assumed. I filed #4564 for that.
Closes#4553
### Screenshots
<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->
I have no screenshot. The picker looks the same; only the list behind it
changes. `apps/console/src/components/SourceField.test.tsx` covers it:
the picker shows the images the option source serves and not the raw PVC
names.
### Downstream repositories
<!--
Cozystack is upstream for repositories that are NOT kept in sync with it
automatically, and nothing in CI compares the two sides, so a change
here can break them silently.
Which change forces what, and which file to touch, is the trigger map in
docs/agents/contributing.md under "Downstream Repositories". Walk it
before you tick anything.
IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM
TO FILL IN:
1. Walk the trigger map against the actual diff of this PR, file by
file. Do not decide from the PR title or from your memory of what you
changed.
2. For every repository you tick, open the follow-up PR there and link
it on the same line. A ticked box with no link claims work that does not
exist, which is worse than an empty box. Search that repository for an
open PR or issue covering it first, and link that instead of filing a
duplicate.
3. When the follow-up is out of scope, or needs a decision that is not
yours to make, open an issue in that repository instead, link that, and
say so in the PR body. Do not push a speculative PR into another
repository just to fill a line here.
4. Do not tick "No downstream repository is affected" to make the
checklist go away. If you are unsure, leave every box empty and say so
in the PR body, so a human decides.
-->
- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:
### Release note
<!-- Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->
```release-note
fix(api): the VM image picker no longer offers golden images whose import failed. The console now reads images from the image option source, and tenants no longer get read access to PersistentVolumeClaims in cozy-public.
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* The image picker now displays available images from the tenant’s image
options.
* **Bug Fixes**
* Images whose imports have failed are excluded from the picker. Images
remain available when import status is still in progress, has succeeded,
or cannot be retrieved.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- Thank you for making a contribution! Here are some tips for you:
- Use Conventional Commits for the PR title: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scopes are not an exhaustive list — pick the most specific scope for
the change and extend the list when a genuinely new area appears.
Examples:
- System components: dashboard, platform, operator, cilium, kube-ovn,
linstor, fluxcd, cluster-api
- Managed apps: postgres, mariadb, redis, kafka, clickhouse,
virtual-machine, kubernetes
- Development and maintenance: api, hack, tests, ci, docs, maintenance
- Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or
add a `BREAKING CHANGE:` footer
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `kind/backport` if it's a bugfix that needs to be
backported to a previous version.
-->
## What this PR does
One transient error from proxy.golang.org is enough to fail an image
build or the `Unit & controller tests` job today. The error is an HTTP/2
stream reset (`stream error ... INTERNAL_ERROR`), and nothing retries or
falls back after it. This PR adds three things: a direct fallback, a
bounded retry and a module cache.
Image builds now fall back to fetching modules from their origin when
the proxy fails. They get `GOPROXY=https://proxy.golang.org|direct` as a
build arg from `hack/common-envs.mk`, and every Go builder stage
declares `ARG GOPROXY`. With a pipe Go goes direct on any proxy error.
With Go's default comma it does that only on a 404 or 410. The value
comes from `IMAGE_GOPROXY` and not from the host's `GOPROXY`, because
buildkit can't reach a mirror on localhost or a `file://` proxy. Every
workflow job that runs Go on the runner sets the same value as env.
Explicit module downloads in Dockerfiles are retried three times with a
growing pause. The loop is inline, in one form that the contract test
pins. I tried a shared script from a named build context first, but
buildx refuses any `--build-context` for the four Dockerfiles still
pinned to the `dockerfile:1.2` frontend. The images also build from
different contexts, so no single file can be copied in. Raising those
pins changes how those images build and needs a real build to check, so
I left it for a follow-up. Two alpine stages, token-proxy and
shadowbox's `ss-server`, left the download to `go build`. The alpine
golang images have no git, so the direct fallback can't fetch for them,
and they now get the same download step. The runner jobs use
`hack/go-mod-download.sh` with the same bound.
The unit test job now starts from a module cache instead of downloading
everything. It restores `GOMODCACHE` with `actions/cache`, keyed on
every `go.sum` in the tree, and then prefetches all tracked modules
through the retry, so a cache miss still works. A cache saved by a PR
run is visible only to that PR, but one saved on the default branch can
be restored by any PR
([docs](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#restrictions-for-accessing-a-cache)).
That is why the build cache warmer on main writes the same key before
its image build. This way the first run of a PR, which is the one that
flakes, starts warm. The warmer restores only an exact key, so old
module versions don't pile up, and its Go steps can't fail the image
build. Both runner labels are Linux, so the key matches. Both also check
out under `/home/ubuntu/_work`, so `go env GOMODCACHE` should give the
same cache path, but I have not seen it on the warmer's runner yet. The
release jobs in `tags.yaml` and `promote-rc.yaml` get the GOPROXY env
only, no cache.
`hack/go-module-fetch-contract.bats` covers all three. Each test is red
on main, and I checked each guard with a mutation.
Closes#4591
### Screenshots
<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->
### Downstream repositories
<!--
Cozystack is upstream for repositories that are NOT kept in sync with it
automatically, and nothing in CI compares the two sides, so a change
here can break them silently.
Which change forces what, and which file to touch, is the trigger map in
docs/agents/contributing.md under "Downstream Repositories". Walk it
before you tick anything.
IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM
TO FILL IN:
1. Walk the trigger map against the actual diff of this PR, file by
file. Do not decide from the PR title or from your memory of what you
changed.
2. For every repository you tick, open the follow-up PR there and link
it on the same line. A ticked box with no link claims work that does not
exist, which is worse than an empty box. Search that repository for an
open PR or issue covering it first, and link that instead of filing a
duplicate.
3. When the follow-up is out of scope, or needs a decision that is not
yours to make, open an issue in that repository instead, link that, and
say so in the PR body. Do not push a speculative PR into another
repository just to fill a line here.
4. Do not tick "No downstream repository is affected" to make the
checklist go away. If you are unsure, leave every box empty and say so
in the PR body, so a human decides.
-->
- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:
### Release note
<!-- Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->
```release-note
ci(build): Go module downloads in image builds and CI jobs now fall back to direct fetching and retry on transient proxy.golang.org errors. The unit test job reuses a module cache written from main.
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Reliability**
- Go module downloads now fall back to direct access when the public
proxy encounters errors.
- Builds and automated checks retry failed module downloads, helping
them recover from temporary network issues.
- **Performance**
- CI caches Go modules to reduce repeated downloads and speed up builds
and tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The marketplace and console sidebars rendered a fixed IaaS / PaaS / NaaS
list and dropped every other spec.dashboard.category. A repository tapped
with cozypkg that brings its own category was reachable only through the
full application list, and its installed applications had no console
section. Follow groupByCategory instead, the order the marketplace page
already uses, and keep Administration in the Admin portal.
Assisted-by: LLM
Signed-off-by: tym83 <6355522@gmail.com>
The console defines a lint script next to typecheck and test, but no
workflow ran it, so new findings landed unseen next to the old ones.
The backlog is cleared down to three react-hooks/exhaustive-deps
warnings. eslint exits zero on warnings alone, so the lint script caps
them at that count: the step gates from its first run, and a new
warning fails it.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Each of these effects re-syncs local state to something that changes
outside the component. The rule's suggested rewrite derives the state
during render instead, which changes when the new value is committed
and what the first render shows, so each site carries a scoped disable
with its reason rather than a rewrite.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Vite's fast refresh can only hot-swap a module that exports nothing but
components; a file that also exports a hook, a helper or a registry
object forces a full page reload on every edit to it. Hooks, context
objects and the RJSF registry now live in their own modules, and the
components stay where they were.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The tab never reads it; the prop stays in the interface because the
detail page passes the same props to every tab.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
client.list was called without a type argument, so its items came back
as unknown and were cast to any to read metadata.name.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
RJSF's generic defaults are any, and the templates restated them, so
every schema read went untyped. The templates now default to unknown
and FormContextType, and the source widgets read option schemas
through a small local interface.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The console's own rules forbid any, and eslint flags every one of these.
The create pages only read a handful of reference fields, so a local
interface per page covers them; the rest of the spec still passes
through from RJSF untouched.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The console badge followed Ready alone, and Ready follows the Helm
release. A vm-disk install does not wait on its DataVolume, so a disk
CDI never populated was painted green.
The badge now shows the WorkloadsReady condition while Ready is True
and WorkloadsReady is not True with reason DataVolumeNotReady. Other
causes of WorkloadsReady=False keep the badge as it was: a stopped VM
has no pod and reports not operational on purpose, and turning it
amber for every kind needs its own decision. A failing Ready still
wins, because it is the more specific cause.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
A transient proxy.golang.org HTTP/2 stream reset fails an image build
outright: Go's default GOPROXY falls back to direct only on a 404 or
410. Image builds now get GOPROXY=https://proxy.golang.org|direct as a
build arg from hack/common-envs.mk, declared by every Go builder stage,
so any proxy error falls back to fetching from the origin. The value
comes from IMAGE_GOPROXY rather than the host's GOPROXY, which may
name a mirror that buildkit cannot reach. The alpine golang images
carry no git, so in their stages a module hosted on GitHub cannot be
fetched directly and the retry is the cover.
Explicit `go mod download` steps retry three times with a growing
pause. The loop is inline, in one form the contract test pins: the
images build from different contexts, so no shared script can be
copied in, and a named build context in the shared buildx arguments
makes buildx refuse every Dockerfile pinned to a frontend older than
dockerfile:1.4.
The two alpine stages that left the download to `go build` get the
same download step, because without git neither the fallback nor a
retry covered them.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
## What this PR does
Fixes#4611.
Since #4148 a tenant may read the `<release>-config` ConfigMap that
holds the FoundationDB cluster file, but the console has no view for
ConfigMaps, so the connection string was still reachable only with
kubectl.
Chart:
- `<release>-resourcemap` is rendered regardless of
`monitoring.enabled`, because the console now reads it;
- the tenant Role grants `get`, `list` and `watch` on
`<release>-resourcemap` next to `<release>-config`, still by
`resourceNames`, with no wider ConfigMap access.
Console:
- the application page gets a ConfigMaps tab when the application's
resource map lists at least one ConfigMap in the same namespace;
- each listed ConfigMap is read and watched with a `metadata.name` field
selector, which RBAC checks against `resourceNames`, so a tenant never
needs `list` on all ConfigMaps;
- values are shown as plain text with a copy button, the cluster file
needs no decoding;
- a resource map that is missing or forbidden hides the tab, a listed
ConfigMap that cannot be read shows the API error instead of looking
empty, and a malformed map or an invalid name is reported.
The tab is generic, but today only FoundationDB ships a resource map, so
it is the only application that gets it.
#3490 keeps `monitoring.enabled` as the switch for the resource map and
asks whether the map should render unconditionally. This PR answers that
for FoundationDB by rendering it always, so the wording in #3490 would
need to follow if both land.
Tests: `helm unittest` for the chart (6 tests), `tsc --noEmit`, `vitest
run` (438 tests in 60 files) and `eslint` on the changed files pass. The
new tests cover reading only the listed names in the active namespace,
the watch, copying, a clipboard failure, a read denial, and malformed
maps. The chart renders with `monitoring.enabled` true and false.
### Screenshots
The ConfigMaps tab of a FoundationDB application, rendered by the
console from this branch against a local stub API with synthetic data:

### Downstream repositories
Walked the trigger map against the diff: it changes a FoundationDB chart
template and its test, and the console. No `values.yaml`,
`values.schema.json`, README, CRD or `hack/` file is touched. The
website FoundationDB page is generated from the chart README and says
nothing about connecting, and the Terraform provider does not read these
ConfigMaps. The console is vendored in this repository.
- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:
### Release note
```release-note
fix(dashboard, foundationdb): the application page shows the FoundationDB connection ConfigMap with the cluster file to tenants who may read it
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Application details now include a ConfigMaps tab when configuration
maps are available. View their values and copy them to the clipboard.
* The tab displays loading, empty, unavailable, and error states.
* **Bug Fixes**
* The resource map remains available when monitoring is disabled, and
its access permissions are included.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Users who provision a service through the dashboard had no hostname to
connect to: the Services tab showed only the raw LoadBalancer IP, while
the pre-1.4.0 UI composed <service>.<root host> for every Service. Render
<service>.<suffix> under the External IP of every LoadBalancer Service
(also while the IP is still Pending), and nothing for ClusterIP Services.
The suffix is config.json serviceDomain, set by the platform value
publishing.serviceDomain. There is deliberately no fallback: the app
charts fix their external name as <release>.<tenant host> while the
LoadBalancer Services are named <release>-external-write and similar,
so a name derived from the tenant apex would match neither the DNS nor
the certificate. Without a configured suffix nothing is rendered.
Runtime config previously reached only the Shell as a prop; it is now
published through AppConfigContext and read from there by the Shell and
the Services tab alike, so a single test on the rendered version pins
the wiring.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The console loads config.json once at startup and treats a missing
serviceDomain key as "show no hostname", so the chart copies
_cluster.service-domain into config.json only when it is non-empty rather
than shipping an empty string the SPA would have to special-case. The
helm-unittest suite pins the unset, empty and set cases.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>