Skip to content

Fix ssl db query params#5196

Open
bmo-at-a9s wants to merge 4 commits intocloudfoundry:developfrom
anynines:feature/fix-ssl-db-query-params
Open

Fix ssl db query params#5196
bmo-at-a9s wants to merge 4 commits intocloudfoundry:developfrom
anynines:feature/fix-ssl-db-query-params

Conversation

@bmo-at-a9s
Copy link
Copy Markdown
Contributor

Description

The fallback parsing of cf services was neglecting the fact that the database URI might contain query parameters.
This PR introduces changes to accomodate them, parse them out correctly and add them to the configuration struct.

Motivation and Context

How Has This Been Tested?

Unit tests have been added to cover this functionality. Additionally, a staging deployment including a database uri with query params was used to verify that the change works.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Docs update
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have followed the guidelines in CONTRIBUTING.md, including the required formatting of the commit message

norman-abramovitz added a commit that referenced this pull request Apr 13, 2026
The fallback parsing of cf services was neglecting the fact that the
database URI might contain query parameters. Update the regex to
capture the optional ?key=value portion, parse it into a map, and
add it to DatabaseConfig as a new QueryParams field.

Tests cover URIs both with and without query params to confirm the
regex handles both cases.

Cherry-picked from upstream PR #5196 (author: Jan-Robin Aumann).

Co-authored-by: Jan-Robin Aumann <jaumann@anynines.com>
norman-abramovitz added a commit that referenced this pull request Apr 15, 2026
* Remove ComponentFactoryResolver (FWT-888)

Replace deprecated Compiler/ngModuleFactory/
ComponentFactoryResolver pipeline with direct
dynamic imports for home card components.
Both CF and K8s entity generators now return
the component class via import().then() instead
of compiling module factories at runtime.

* Replace first() with take(1) codebase-wide (FWT-907)

Replace 295 bare first() calls with take(1) across
157 files to eliminate EmptyError exceptions in
test environments. Add defaultIfEmpty guards on 15
critical paths (steppers, backup/restore, home page,
user profile, deploy steps). Fix two NG0203 errors
where toObservable() was called outside injection
context in create-release and chart-values-editor.

* Add ApplicationDeploySourceTypes provider (FWT-888)

The CF home card component injects this service
but it is not providedIn root. Previously supplied
by CFHomeCardModule which is now bypassed by the
direct dynamic import. Add to component providers
and update spec to use overrideComponent.

* Detect platform mismatch in dev backend (FWT-840)

Check binary architecture before starting the dev
backend server. Rebuild for the host platform if
the existing binary was cross-compiled for deploy.

* Remove dead wrapper NgModules (FWT-872)

Delete CFHomeCardModule, KubernetesHomeCardModule,
and MonocularModule. All wrapped components that
are already standalone. No consumers remain after
the ComponentFactoryResolver migration.

* Guard against panic in K8s cert auth (FWT-872)

Add bounds checking in extractCerts to prevent
panic when request body has no colon delimiter.
The body is consumed by BindOnce before reaching
extractCerts — a pre-existing bug that needs a
deeper fix to pass cert data as form fields.

* Fix K8s auth body consumption bug (FWT-872)

BindOnce in loginToCNSI consumed the request body before
K8s auth handlers could read it. All handlers now read
auth data from form fields instead of raw request body.
Also strips embedded whitespace from token paste.

* Add editable path and clear to file input (FWT-872)

File input now supports typing a path, clearing a
selected file, and browsing via file picker.

* Convert K8s SCSS to Tailwind, fix chart colors (FWT-872)

Convert 5 K8s component SCSS files to Tailwind utility
classes. Restore missing chart color swatches lost during
Tailwind migration — fixes invisible gauges in light mode.

* Fix list filter, icon font, and side panel bugs

Add Material Symbols Outlined font for icon rendering.
Fix list filter not restoring all items by simplifying
splitCurrentPage and removing switchMap re-subscription.
Fix table expander TypeError when config is pre-evaluated.
Fix resource viewer side panel with markForCheck on setProps.

* Remove unused imports from AboutPageComponent

* Convert 28 small K8s SCSS files to Tailwind (FWT-872)

Batch convert remaining small SCSS files in the K8s
package to Tailwind utility classes. Residual SCSS
kept only for child component selectors and mat-icon
sizing that Tailwind cannot express.

* Fix resource viewer missing pipe and component imports

Add TitleCasePipe, DatePipe, RouterModule, MetadataItem,
and JsonViewer imports to KubernetesResourceViewerComponent.
Missing imports caused template rendering to fail silently,
producing empty side panel content.

* Fix RouterLink import and null ConfigMap data guard

Use RouterLink directive instead of RouterModule in
standalone component imports. Guard against null data
in ConfigMap/Secret entity column definitions.

* Fix K8s home card counts not loading (OnPush)

Add markForCheck after setting count observables in
load() — OnPush change detection needs explicit trigger
when observables are assigned after initial render.

* Simplify service ports display, top-align table rows

Replace nested sub-table with compact inline format
showing port/protocol (name) → targetPort :nodePort.
Change table row alignment from center to top for
better readability with multi-line cells.

* Fix missing cellDefinition binding in table rows

Wire up [cellDefinition] on app-table-cell so columns
with valuePath (clusterIP, type, replicas, etc.) render
their values. Simplify service ports to inline format.

* Migrate e2e suite to Playwright

Port Protractor tests to Playwright with new page objects,
fixtures, and helpers. Tests run against local Stratos
(local auth) or remote (SSO); all core tests pass or
skip gracefully when environment lacks CF data.

- Add auth helpers: detectAuthType, browserLogin (local+SSO)
- Add page objects: APIKeysListPage, EndpointsPage, etc.
- Add list/table component helpers with findRow, getRowCount
- Fix view-toggle detection: isEnabled() not isVisible()
- Fix gate-check script: test-headless -> test

* Fix autoscaler specs missing HttpClient provider

StratosBrandingService now injects HttpClient; add
provideHttpClient() to autoscaler component test setups.

* Remove dead list helper, add type=text to filter input

getCurrentPageStartIndex was unused; remove it with its
tests. Add explicit type="text" to filter <input> so
Tailwind @tailwindcss/forms class strategy styles it.

* Use Tailwind forms class strategy, remove input shadows

Switch @tailwindcss/forms to class strategy so it only
styles elements with .input/.select, preventing accidental
global input resets. Replace shadow-sm with shadow-none.

* Migrate K8s/Helm package SCSS to Tailwind

Delete empty SCSS files and remove their styleUrls/styleUrl
references. Migrate SCSS content to Tailwind @apply directives
or inline classes. Delete unused _mixins.scss and theme.scss.

* Fix E2E test timing for CF-backed list tests

Wait for isLoadingPage$ to clear (filter enabled) after
goToAppsPage returns, so view toggles and filter input
are interactable. Increase getItemCount timeout in
list-filter and test timeouts for CF-latency-sensitive
session memory tests.

* Upgrade Angular 20.3.9→20.3.18, lodash-es, x/crypto

Update Angular across all 8 frontend sub-packages from 20.3.9
to 20.3.18, fixing 5 XSS advisories (GHSA-58c5, GHSA-g93w,
GHSA-jrmj, GHSA-prjf, GHSA-v4hv). Remove redundant Angular
overrides from root package.json (sub-packages now declare
correct version). Upgrade lodash-es to 4.18.0 (fixes
GHSA-f23m, GHSA-r5fr). Bump golang.org/x/crypto to v0.49.0
in jetstream modules (fixes CVE-2025-47914, CVE-2025-58181).

* Fix Firefox E2E failures and proxy /api-keys routing bug

proxy.conf.cjs: change "/api/" key to "/api/v1" — Angular CLI
strips trailing slashes from proxy keys, causing "/api/" to match
"/api-keys" and forward it to the backend (404). Using "/api/v1"
matches only actual API routes.

auth.helper.ts: catch Firefox NS_BINDING_ABORTED on login redirect
by retrying waitForURL — Firefox aborts the old binding mid-redirect
but the navigation succeeds; the retry lets it complete.

api-keys.spec.ts: wait for list or no-content element before
branching — prevents false "not found" when the page is still
loading after networkidle timeout.

* ESLint config: vitest globals pattern + auto-fix (1826→788 warnings)

Add varsIgnorePattern for vitest lifecycle imports in test files,
eliminating ~974 false-positive no-unused-vars warnings. Run eslint
--fix for auto-fixable rules (boolean cast, useless escape). Phase 1
of FWT-876 ESLint cleanup.

* Fix ESLint warnings: eqeqeq, case-declarations, negated-async, types

Mechanical fixes across 32 files (788→723 warnings):
- Template == to === and != to !== (autoscaler, k8s)
- Wrap switch case bodies with lexical declarations in { }
- Replace !(obs$ | async) with === false/null as appropriate
- Replace {} type annotations with Record<string, unknown>
- Replace .hasOwnProperty() with Object.hasOwn()
Phase 2-3 of FWT-876 ESLint cleanup.

* Add check verb, FINAL and DRYRUN variables, remove bump release

New features:
- make check (lint|gate|tests|coverage|e2e) — quality gate verb
- FINAL=strip — strip prerelease from version, persisted to package.json
- DRYRUN=yes — cross-cutting dry-run variable (bump supported)
- Remove bump release modifier (replaced by FINAL=strip on release)

* Migrate remaining constructor injection to inject() function

Convert 8 files from constructor parameter DI to inject() pattern.
Handles super() calls, non-injectable string params (eslint-disable),
and Effects classes. Completes FWT-874 inject migration (24→0 warnings).

* Fix accessibility and remaining template ESLint warnings

Add alt text to images, tabindex + keydown handlers for interactive
non-button elements, associate labels with form controls. Fix stray
eqeqeq in autoscaler step3. Completes FWT-875 accessibility fixes.
1826→681 warnings total (63% reduction across FWT-876).

* Remove dead imports and expand ESLint unused-vars ignore patterns

Remove 59 dead RxJS operator and Angular type imports across 50 files.
Expand varsIgnorePattern for test files (fixture, component, etc.) and
add argsIgnorePattern/varsIgnorePattern for underscore-prefixed params.
1826→572 warnings (69% reduction). Remaining 492 unused-vars across
244 files need per-file review in a follow-up session.

* ESLint: ignore _-prefixed caught errors and spec vars

Add caughtErrorsIgnorePattern: "^_" to both source and spec
file rule blocks. Update spec varsIgnorePattern from
literal "_" to "_\w*" to match underscore-prefixed names.

Enables _-prefix convention for intentionally unused catch
clause variables and test setup variables.

* Fix TS build errors from inject() migration

Subclasses still passed constructor args to base classes that
were migrated to inject(). Service list configs passed 5 args
where the base now takes only a URL string. Two value-position
Record<string, unknown> typos crashed the compiler.

- super(): drop now-empty args in autoscaler step 1-4 components,
  github-commits configs, cf-space-permission-cell
- service-instances list configs: pass only the URL string arg
- bind-apps-step: type bindingParams as Record<string, unknown>
- helm-release-resource-graph, variables-tab: replace literal
  Record<string, unknown> in value position with {}
- setup.actions SetupSuccess: payload type any (HTTP response
  object is not assignable to Record<string, unknown>)
- table-cell.component: Type<Record<...>> -> Type<any> for
  ViewContainerRef.createComponent compatibility

Build now succeeds; E2E core suite passes (71/0).

* FWT-876: Reduce ESLint unused-vars warnings 532 to 5

Mechanical cleanup pass across 260 files. Five remaining
warnings are intentional T type parameters on public API
interfaces (CfAPIResource, IBaseListAction,
TailwindSnackBarRef, UniquenessValidatorConfig,
KubernetesPodTagsComponent).

- Prefix unused function args, catch clause vars and assigned
  locals with _ (script-applied; destructured props use
  rename syntax: { name: _name } not { _name })
- Remove unused imports left over from inject() and take(1)
  migrations (Store, CFAppState, ChangeDetectionStrategy,
  first, ChangeDetectorRef, signals imports never wired up,
  etc.)
- Remove dead local interfaces and helper functions
  (DomainFormModel, IValueLabels, getUniqueKeys, etc.)
- Remove unused 'T' generics from non-public methods
- Block-disable unused-vars on test framework barrel
  re-exports in core-test.helper.ts

All unit tests pass; build clean; E2E core suite green.

* Pin patched hono and vite via overrides

GitHub Dependabot flagged 13 new alerts since last push:
- hono < 4.12.12 (5 medium): cookie name handling, IP
  matching, serveStatic middleware bypass, toSSG path
  traversal
- @hono/node-server < 1.19.13 (1 medium): same serveStatic
  bypass
- vite <= 6.4.1 (7 high+medium): arbitrary file read via
  dev server WebSocket, server.fs.deny bypass, .map path
  traversal

All come in transitively (hono via @angular/cli ->
@modelcontextprotocol/sdk; vite via build tooling). Pin
patched versions through package.json overrides so the
bumps stay in semver-patch range without making them
direct dependencies. vite stays in 6.x (no major bump).

Build and gate check (1104 tests) pass.

* Fix Helm chart browsing broken by standalone migration

Two regressions surfaced when smoke-testing the helm/monocular
pages against a real Helm repo:

1. createMonocularProviders required HTTP_INTERCEPTORS but the
   root app uses provideHttpClient(withInterceptors([...])) — the
   functional API does not register the legacy HTTP_INTERCEPTORS
   multi-provider. Result: NullInjectorError when navigating to
   /monocular/charts and chart-details. Fixed by marking the
   dependency Optional() and falling back to an empty array.

2. ChartDetailsComponent uses ChangeDetectionStrategy.OnPush but
   sets this.chart, this.currentVersion etc. inside an imperative
   subscribe(). With OnPush those assignments do not trigger a
   re-render and the chart-details page stayed blank. Inject
   ChangeDetectorRef and call markForCheck() in finalize().

Verified end-to-end against k3d + prometheus-community Helm repo:
catalog renders, chart-details renders with README, version
sidebar, dependencies, install instructions.

* FWT-872: Convert helm/monocular SCSS to Tailwind, remove dead code

Reduce helm/monocular SCSS from 477 lines to 54 lines (89%)
while removing 651 lines of orphaned code from an older Helm
chart browsing implementation that has been replaced by
CatalogTabComponent + ListComponent + MonocularChartCard.

Dead code removed (entire directories, no external refs):
- monocular/charts/         (ChartsComponent, 372 lines)
- monocular/chart-index/    (ChartIndexComponent, 110 lines)
- monocular/chart-list/     (ChartListComponent, 68 lines)
- monocular/list-filters/   (ListFiltersComponent, 57 lines)
- monocular/app.component.scss (orphan, 44 lines)

SCSS converted to Tailwind utilities in templates:
- chart-details.component.scss      (91 -> 0): header BEM block
  was dead code (header is rendered by entity-summary-title);
  remaining classes flattened to template
- chart-details-usage.component.scss (46 -> 0): all classes
  except __install were dead; __install moved to template
- chart-details-versions.component.scss (41 -> 0): internal
  table BEM classes inlined as Tailwind in template
- chart-details-info.component.scss (23 -> 0): chartInfo BEM
  inlined; dead app-panel descendant rule removed
- chart-item.component.scss (18 -> 0): entirely dead
  (.chart-item-info classes never used in template)
- loader.component.scss (1 -> 0): empty placeholder

SCSS kept but flattened (BEM removed, SCSS vars inlined):
- list-item.component.scss (43 -> 35): content projection
  consumers (chart-item) reference these classes externally,
  so styles must apply via CSS rather than template Tailwind
- panel.component.scss (19 -> 19): ngClass dynamic bindings
  reference --container/--background/--border modifiers

Build clean. Smoke tested end-to-end against k3d +
prometheus-community Helm repo: catalog (47 charts), filter,
sort, pagination, and chart-details (README, version sidebar,
maintainers, related links) all render identically.

* FWT-872: Remove 34 empty K8s placeholder SCSS files

Clean up empty .scss files left over from prior Tailwind
migration steps. Each file contained only the comment
"Styles moved to Tailwind classes in template" or was
fully empty. Removed both the .scss file and the matching
styleUrls entry from each component decorator.

No template or class changes — purely removes references
to files that contributed nothing to the bundle.

* Remove 125 empty placeholder SCSS files across packages

Same cleanup as the K8s pass, extended to the rest of the
frontend. Each removed .scss file contained only a comment
or was completely empty, with the styles long since moved
into Tailwind utilities in the templates. Removed both
the file and the matching styleUrls entry.

By package:
- cloud-foundry: 92 files
- core:          26 files
- example-extensions: 3 files
- cf-autoscaler: 2 files
- git, shared:   1 file each

For two components (quota-definition, space-quota-definition)
the styleUrls had a second array entry pointing at a shared
quota-definition-base.component.scss — kept that entry and
removed only the empty self-stylesheet.

No template or class changes; bundle is unaffected.

* WIP: ng update @angular/core@21 @angular/cli@21

* Angular 21 upgrade WIP

* WIP: vite 7 + vitest 4 migration, peer deps bumped

* Document Angular 21 upgrade WIP state

Comprehensive status doc explaining what works (build, E2E,
dev server) and what blocks completion (NG0203 in vitest 4
unit tests). Includes:

- All version bumps applied
- All code changes for Angular 21 breaking changes
- The exact NG0203 failure mode and debugging hypotheses
- Five things tried that didn't fix the test infra issue
- Three resumption strategies (wait, invest a focused day,
  or abandon and revisit)
- Resume-from-cold checklist with the relevant files

This branch should be kept as a backup only — ship work
continues on feature/Angular-21 (still at Angular 20.3.18)
until @analogjs/vite-plugin-angular and vitest 4 catch up
to Angular 21 + zoneless compatibility.

* cf-stratos: fix NG0203 root cause + vitest 4 pool migration

Under vitest 4 + Angular 21, two instances of
@angular/core/fesm2022/_not_found-chunk.mjs were being loaded in the same
process — one via Node's native ESM loader (triggered by externalized
@ngrx/store) and one via VitestModuleEvaluator's transform pipeline. Each
instance had its own module-level _currentInjector state, so NgRx Store's
factory called getCurrentInjector() on the wrong instance and threw
NG0203: StateObservable token injection failed.

Force all Angular-dependent libraries through vitest's transform pipeline
via server.deps.inline + ssr.noExternal so there's a single module graph:
  - /^@angular\//
  - /^@ngrx\//
  - /^@analogjs\//
  - 'ng2-charts'

Also migrate the deprecated test.poolOptions block to vitest 4 syntax:
  - poolOptions.forks.singleFork: true -> maxWorkers: 3 (at test: level)
  - poolOptions.forks.isolate: false -> isolate: true (at test: level)

isolate:true gives each test file a fresh VM context, preventing the
cross-file state pollution that was masking real bugs under the old
singleFork+isolate:false layout. maxWorkers:3 keeps wall time reasonable
(~1.8x speedup vs single worker).

Applied identically to all 8 per-package vitest configs.

Effect on full suite:
  Before: 234 failed / 866 passed / 9 skipped (1109 total)
  After:  0 failed / 1100 passed / 9 skipped (1109 total)

* cf-stratos: break circular @stratosui/core barrel imports in core/

When a file inside core/src/ imports from the @stratosui/core barrel
(public-api.ts), loading that file triggers public-api.ts to evaluate,
which re-exports shared.module.ts, which in turn imports the original
file — before public-api.ts has finished initializing. The result is an
undefined slot somewhere in SharedModule's imports array, which Angular
21 later trips over with:

  TypeError: Cannot read properties of undefined (reading 'ngModule')
  TypeError: Cannot read properties of undefined (reading 'ɵcmp')
  at isModuleWithProviders / isStandaloneComponent
  at SharedModule2.get / CreateEndpointModule2.get

The `2` suffix is Angular's JIT compiler deconflicting what looks like
two copies of the same module (really: one full copy + one partial copy
from the cycle).

Under Angular 20 + vitest 3 the NG0203 error fired first so these never
surfaced; with that fixed, they became visible as 3 of the 5 remaining
core failures.

Fix: inside core/src/, import from direct relative paths instead of the
@stratosui/core barrel. Seven production files affected:

  table-cell-endpoint-name.component.ts (CustomTooltipDirective)
  logout-page.component.ts               (CardWrapperComponent)
  eula-page.component.ts                 (CustomTooltipDirective)
  events-page.component.ts               (CustomTooltipDirective)
  backup-connection-cell.component.ts    (CustomTooltipDirective)
  card-number-metric.component.ts        (UtilsService)
  create-endpoint-helper.ts              (CurrentUserPermissionsService,
                                          StratosCurrentUserPermissions,
                                          UserProfileService,
                                          SessionService)

The @stratosui/core barrel is still the correct import path from other
packages — only in-package imports need to be direct.

* cf-stratos: initialize EntityCatalogHelper in 4 entity-touching specs

Four component specs instantiate components whose constructors or input
setters call stratosEntityCatalog.<entity>.store.*, which requires the
EntityCatalogHelper singleton to have been set via
EntityCatalogHelpers.SetEntityCatalogHelper(helper). Under the old
isolate:false test layout these specs happened to inherit the helper from
an earlier spec in the same fork; under the new isolate:true layout each
file gets a fresh VM context and must initialize the helper itself.

These were pre-existing test bugs masked by the NG0203 failures — three
fail with 'EntityCatalogHelper not initialized' and one with
'Cannot read properties of undefined (reading \\'store\\')' once NG0203 is
out of the way.

Fixes:

  table-cell-endpoint-name.component.spec.ts
    - register stratos entities via entityCatalog.register()
    - inject EntityCatalogHelper and call SetEntityCatalogHelper()

  running-instances.component.spec.ts
    - add STORE_TEST_PROVIDERS (brings ENTITY_SERVICE_FACTORY_TOKEN and
      friends into the test providers)
    - inject EntityCatalogHelper and call SetEntityCatalogHelper()

  cloud-foundry-events.component.spec.ts
    - inject EntityCatalogHelper and call SetEntityCatalogHelper()
      (providers were already complete)

  helm-release-history-tab.component.spec.ts
    - mock HelmReleaseHelperService with useValue instead of providing
      the real one, avoiding its constructor's call into
      workloadsEntityCatalog.release.store. Mirrors the pattern already
      used by upgrade-release.component.spec.ts.

With these in place the full vitest suite is green:
  0 failed / 1100 passed / 9 skipped (1109 total) in 377s

* cf-stratos: use strict equality in autoscaler policy templates

@angular-eslint/template/eqeqeq flags loose `==` / `!=` in Angular
templates and wants `===` / `!==`. The rule is correct — Angular
templates should use strict equality to match TypeScript conventions.

Under the angular-eslint 21.x + ESLint 9.x combo pulled in by the
Angular 21 upgrade, the rule's auto-fix path also crashes with a
TypeError when it encounters these operators:

  TypeError: Cannot destructure property 'source' of
    '(0, get_nearest_node_from_1.getNearestNodeFrom)(...)' as it is null.
    at getFix (@angular-eslint/eslint-plugin-template/dist/rules/eqeqeq.js:81)

The crash aborts the entire lint phase, blocking `make check`. This
commit fixes all 14 loose-equality instances across the 4 autoscaler
edit-policy templates (step2, step3, step4, and the parent). No
functional change — editIndex is always a number so `==`/`===` behave
identically here.

After this commit `bun run lint` completes with 115 warnings, 0 errors.

* cf-stratos: fix nil res deref in metrics/main.go (go vet)

The Angular 21 branch's new `make check` target runs `go fmt && go vet`
as part of the lint gate (Makefile refresh from 2026-04-07). That
surfaced a latent nil-pointer bug in plugins/metrics/main.go:

  res, err := httpClient.Do(req)
  defer res.Body.Close()              // ← go vet flags this
  if err != nil || res.StatusCode != http.StatusOK {

go vet's SA5011 check caught it: if httpClient.Do returns an error,
`res` is nil and the deferred `res.Body.Close()` will panic when the
function returns. The error branch never gets to execute.

Reorder to check err first, then defer, then check status — mirroring
the pattern already in use higher up in the same file (line 185-198):

  res, err := httpClient.Do(req)
  if err != nil {
    log.Errorf("Error performing http request: %v", err)
    return "", api.LogHTTPError(res, err)
  }
  defer res.Body.Close()
  if res.StatusCode != http.StatusOK {
    log.Errorf("Error performing http request - response: %v", res)
    return "", api.LogHTTPError(res, err)
  }

Also includes a trailing-whitespace cleanup in
datastore/20240818042100_RetryEndpointCACert.go picked up by
`go fmt` during the same make check run.

After this commit `go vet ./...` on the whole jetstream returns exit 0.

* cf-stratos: bump version to v5.0.0-dev.4 for Angular 21 adepttech deploy

Bumps package.json prerelease tag ahead of the CF deploy to
console.run.adepttech.ca with the Angular 21 upgrade work. Ensures the
About/Diagnostics page shows a unique version string so we can confirm
the new build is live vs the previous dev.3 deploy.

* cf-stratos: widen endpoint register dialog inputs

The Name and Endpoint Address inputs in the Register Cloud Foundry
Endpoint dialog were collapsing to ~180px regardless of the column
width. The cause was a combination of:

  1. The step-1 SCSS declared
       form.stepper-form .custom-form-field { max-width: 450px }
     — but `custom-form-field` is the root class inside
     <app-form-field>'s own template and Angular view encapsulation
     prevented this selector from piercing into the child component,
     so the rule was dead code.
  2. <app-form-field>'s root <div> uses `inline-block w-full`. Without
     a block-level host, the `w-full` collapses to the host's intrinsic
     size (which is `inline` by default), and a hard `min-width: 180px`
     on .form-field-infix pinned the rendered width.

Fix with Tailwind utilities directly on the affected tags — no ::ng-deep,
no new SCSS (per the project's Tailwind-only policy):

  <app-form-field class="block w-full">
    <input class="w-full" ...>
  </app-form-field>

Remove the dead max-width rule from the step-1 SCSS to keep the file
honest.

After: Name and Endpoint Address fields fill their column (~450-500px
at the default modal width).

* cf-stratos: restore previous pageHeader portal on component destroy

PageHeaderComponent is rendered via a TemplatePortal pushed into
TabNavService.pageHeader (a signal, read by <app-show-page-header> in
the app shell). Under the old implementation every instance:

  - ngAfterViewInit: setPageHeader(new portal)  — overwrites
  - ngOnDestroy:     tabNavService.clear()      — unconditional wipe

That assumes there's only ever one live page-header at a time.
Violated when a component loaded inside the endpoint register modal
(e.g. CreateEndpointComponent, K8s/Helm/Git registration components)
has its own nested <app-page-header>. The inner header clobbers the
outer's portal on mount, then on modal close its ngOnDestroy calls
clear() — wiping the signal entirely. The outer endpoints-page's
PageHeaderComponent is still alive but has no mechanism to re-register,
so the whole page-header bar (title + Add button + theme/user menu)
disappears until a hard browser refresh.

Give each instance stack-like discipline without changing the service
API: remember the previous portal on mount, restore it on destroy,
and refuse to clear if someone else is the current owner.

  ngAfterViewInit() {
    this.previousPortal = this.tabNavService.pageHeader();
    this.myPortal = new TemplatePortal(...);
    this.tabNavService.setPageHeader(this.myPortal);
  }

  ngOnDestroy() {
    if (this.myPortal && tabNavService.pageHeader() === this.myPortal) {
      if (this.previousPortal) {
        this.tabNavService.setPageHeader(this.previousPortal);
      } else {
        this.tabNavService.clear();
      }
    }
    // Else: another component is the current owner; leave it alone.
  }

This is the universal fix — protects against any nested page-header
scenario regardless of which registration component is involved.

* cf-stratos: defense-in-depth for endpoint register modal lifecycle

Layered improvements around the register-endpoint modal so that the
underlying Add button + page-header visibility bug can't resurface
through a different code path. The universal fix is the prior commit
(page-header portal restore); these are complementary hardening.

Endpoints page (button visibility):
  - Replace `*appUserPermission="canRegisterEndpoint | async"` with
    a signal-backed `@if (canRegisterEndpoint())`. The directive +
    async pipe combo was sensitive to CD timing under zoneless
    Angular 21; a signal read is tied to the permission result and
    doesn't churn when unrelated state changes.
  - Drop UserPermissionDirective from the component's imports.

UserPermissionDirective (defensive hardening for other callers):
  - Replace one-shot ngOnInit with ngOnChanges. Accept `null |
    undefined` for the input so it survives the first CD cycle when
    an async pipe hasn't emitted yet. Tear down the previous
    subscription and rendered template before re-subscribing.
  - Other templates that still use `*appUserPermission` now respect
    input changes instead of silently locking to the first value.

Endpoint register modal (event handling + nested header):
  - Replace raw `document.addEventListener('keydown', ...)` with
    `@HostListener('document:keydown.escape')`. The raw listener
    bypassed Angular's event pipeline and did not trigger change
    detection under zoneless; the decorator is registered through
    Angular and fires CD correctly. Also fixes the memory leak from
    `.bind(this)` creating a fresh function that removeEventListener
    in ngOnDestroy couldn't remove.
  - When loading a registration component via ComponentRef (either
    the endpoint-specific path or the CreateEndpointComponent
    fallback), set `hideHeader = true` on the instance so its nested
    <app-page-header> is skipped. Prevents the visual duplication
    even on the happy path where portal restore would paper over it.

CreateEndpointComponent:
  - Add `@Input() hideHeader = false`.
  - Wrap the template's <app-page-header> in `@if (!hideHeader)`.
  - Routed full-page usage keeps its header (default); modal usage
    opts out so there's no nested page-header inside the modal.

* cf-stratos: e2e regression tests for register modal close paths

Four Playwright tests covering every way a user can dismiss the
endpoint register modal, pinning the invariant that the Add endpoint
(+) button in the page header must remain visible across any modal
open/close cycle:

  1. Close via the footer Cancel button
  2. Close via the header X icon
  3. Close via the Escape key
  4. Consecutive cycles (Cancel → X → Escape) to catch state
     pollution between cycles

Each test opens the modal, verifies the overlay is visible, closes it
via the target path, verifies the overlay is gone, and asserts the
Add button is still visible.

Regression guard for the whole class of bugs we just fixed: nested
PageHeaderComponent clobbering the TabNavService signal, raw
document.addEventListener bypassing zoneless change detection, and
directive/async-pipe CD timing issues that caused the Add button to
vanish after modal close.

Implementation notes:
  - The `<app-endpoint-register-modal>` host element collapses to 0x0
    because the actual modal content is fixed-positioned and taken out
    of flow. Use `toHaveCount(1)` for host presence, `toBeVisible()`
    on the inner fixed overlay div for visible-to-user assertions.
  - Uses the existing `adminPage` fixture — worker-scoped authenticated
    session, no per-test login cost.

* cf-stratos: bump version to v5.0.0-dev.5 for adepttech redeploy

Bumps the prerelease tag ahead of the next CF deploy to
console.run.adepttech.ca, carrying the endpoint register modal
lifecycle fixes (page-header portal restore, signal-backed Add
button, HostListener for Escape, Tailwind-widened inputs).

Unique version makes the About/Diagnostics page unambiguous about
which build is live compared with the previous dev.4 deploy.

* docs: update for shipped make bump lifecycle and check verb

Cover prerelease stages (alpha/beta/prerelease), automatic build
metadata, FINAL=strip, DRYRUN=yes cross-cutting variable, and the
new make check verb with its five modifiers.

* rename STRATOS_E2E_* env vars to drop redundant prefix

Mechanical rename across 15 live files:
  STRATOS_E2E_BASE_URL → E2E_BASE_URL
  STRATOS_E2E_PROFILE  → E2E_PROFILE
  STRATOS_E2E_ENV      → E2E_ENV
  STRATOS_E2E_WORKERS  → E2E_WORKERS

The repo is named cf-stratos; the STRATOS_ prefix added no namespace
value. Legacy Protractor-era CI scripts in deploy/ci/{travis,automation}
are intentionally not renamed — they reference removed commands and
do not run.

Verified behavior-neutral:
  make check lint  — clean
  make check gate  — vitest 1100/9/0, go tests all pass
  make check e2e   — 76/41/0 in 6.1 min (exact baseline match)

* add E2E_BROWSERS, E2E_TRACE, E2E_VIDEO, E2E_SCREENSHOTS

Make recipe variables for the e2e Playwright runs:
  E2E_BROWSERS=chromium,firefox,webkit  pick projects (or "all")
  E2E_TRACE=on                          force trace capture
  E2E_VIDEO=on                          force video capture
  E2E_SCREENSHOTS=on                    force screenshot capture

Also wire DRYRUN=yes (existing cross-cutting variable, previously
only consumed by bump) to check.e2e and test.e2e — maps to
playwright --list.

Three helpers (_e2e_browsers, _e2e_flag, _e2e_toggle) cover all
current and future e2e variables. Validation is delegated to
Playwright (unknown projects surface "Project 'X' not found").

Defaults are unchanged: empty E2E_BROWSERS falls through to
--project=chromium; empty trace/video/screenshot use the
playwright.config.ts defaults.

Verified:
  make -n check e2e [8 permutations] — helper expansions correct
  make check gate  — vitest 1100/9/0, go tests all pass
  make check e2e   — 76/41/0 in 6.1 min (exact baseline match)

* docs: cover E2E_* recipe variables and DRYRUN extension

Add "E2E recipe variables" subsection under Quality Gates
documenting E2E_BROWSERS, E2E_TRACE, E2E_VIDEO, E2E_SCREENSHOTS
with a values/example table and common-invocations block.

Update the cross-cutting DRYRUN=yes entry to note it now applies
to check e2e and test e2e as well as bump.

* version-bump.sh: hide 'bump release' from user-facing usage text

Word-conflict cleanup follow-through. The 2026-04-07 word-conflict
resolution removed 'make bump release' from the Makefile's BUMP_MOD
filter in favor of 'make release cf FINAL=strip'. But the underlying
version-bump.sh script still advertised 'bump release' in its Commands
list and Examples block, recreating the word-conflict signal at the
script layer.

Remove 'bump release' from the script's user-facing Commands list and
Examples. The internal bump_release function and its case handler stay
in place — they're called by the Makefile's FINAL=strip re-exec block
at line 378. Added a short Note: pointing users to the canonical
'make release cf FINAL=strip' path.

No logic change. No test surface affected. Usage-text-only edit.

* resolve display name when options load after value

custom-select showed the raw form-control value (often a GUID)
when writeValue() ran before async options loaded. options.changes
now re-resolves displayValue after options arrive. Fallback shows
placeholder instead of the raw value to avoid a GUID flash during
load.

* suppress duplicate label on app-select in form-field

custom-form-field rendered a floating label from the child
app-select's placeholder while the select also rendered its own
muted placeholder in the trigger — two overlapping texts on all
22 app-select usages.

Suppress the form-field label entirely when the child is an
app-select. Fix CSS specificity so the select placeholder renders
in the muted color (move text color from static class to
conditional ngClass to avoid same-specificity override).

* add clearing, required marker, and color fixes to selects

Add "None" option to create/edit organization quota dropdowns so
users can clear a selection back to the placeholder state. Selecting
a null-valued option now clears selectedValues rather than storing
null, returning the trigger to its placeholder appearance.

Show a red asterisk on required selects when empty — compensates
for the form-field label suppression from the prior commit.

Hide the success checkmark when no value is present. Use component
CSS classes for placeholder/value text color instead of inline
styles to match the form-field floating label and eliminate a
dark→grey flash on initial render.

* tighten form-field spacing and fix unlimited-input

Remove reserved subscript padding (1.34375em) from form-field
wrapper — errors/hints now take space only when present. Reduce
infix padding from 0.75em to 0.25em and inter-field gap from
mb-4 to mb-2 for denser forms. Adjust floating label position
and underline to match.

Fix unlimited-input: toggle was inverted (never toggled the
unlimited flag on user click). Replace float/margin layout with
flex. Drop required validation when unlimited is checked.

* fix quota form labels, validation, and layout

Make the floating-label system actually render on quota create/edit forms
(org and space) by fixing the ContentChild selector, detecting required
state correctly, and adding AppInputDirective to consumer imports. Also
make the form visually match the console481 reference and remove several
UX annoyances.

Label rendering:
- custom-form-field ContentChild(AppInputDirective, forwardRef) so that
  projected <input appInput> elements are discovered (string 'input' was
  treating it as a template ref that no consumer declares).
- Read required state from input.required property (not hasAttribute)
  and re-read in ngAfterViewInit after projected bindings apply.
- quota-definition-form, space-quota-definition-form, unlimited-input
  now import AppInputDirective so the directive is actually attached.

Label UX:
- floatLabel defaults to 'always' so labels stay above the input and
  never overlap with the input area.
- Drop scale(0.75) on the floating transform (caused blurry text from
  transform-scaled 12px glyphs) and use text-xs directly.
- Move required * to the beginning of the label for scannability.
- Remove transition-all so color state changes (blue/green/red) are
  instant, no lingering previous color.

Validation:
- isValid / isInvalid consider only dirty, not touched, so tabbing or
  clicking through empty required fields does not trigger errors.

Layout:
- Widen .stepper-form max-width 450 -> 600 so long floating labels fit
  without wrapping into the input area.
- Add w-full + placeholder:!text-transparent to inputs (inline Tailwind)
  so fields fill available space and @tailwindcss/forms can't override
  the transparent placeholder.

Checkbox tab order:
- Add tabindex=-1 to the hidden native <input type=checkbox> in the
  custom checkbox so keyboard tab skips it (the visible wrapper div
  already handles keyboard via keydown.enter/space).

unlimited-input state handling:
- Initialize unlimited=false (was !: boolean undefined) to avoid
  NG0100 ExpressionChangedAfterItHasBeenCheckedError.
- Separate onCheckboxChange(event) (reads event.checked from the
  MatCheckboxChange event) from onChange() (applies state based on
  current unlimited). setInitialValues() now calls onChange without
  double-toggling.

* require dirty form on quota edit steps

The Update button on the edit org quota and edit space quota screens
was enabled as soon as the existing quota data passed validation — which
meant opening the page and tabbing through fields without changing
anything left the button active. Change the step validate() to also
require formGroup.dirty so the button only enables once the user has
actually modified something.

Create steps are unaffected: entering required values makes the form
both valid and dirty at the same time.

* convert custom-form-field label to tailwind, drop dead scss

Move the floating-label color/size/position state into the template via
a labelClasses getter that returns a Tailwind class string based on the
component's current state (focused/valid/invalid/neutral). The SCSS no
longer owns any label styling.

Delete rules that never fired because of Angular ViewEncapsulation: all
.form-field-infix input/textarea/select selectors (including the ones
under :focus-visible, reduced-motion, dark-theme, float-label-never,
and print @media blocks). Consumer templates already supply w-full,
placeholder:!text-transparent, etc. directly on their own <input>
elements.

Move :host { display: block } out of custom-form-field.component.scss
and unlimited-input.component.scss into the components' host metadata
(host: { class: 'block' }). The unlimited-input SCSS file had nothing
else in it and is now deleted, along with its styleUrls entry.

Extend tailwind.config.js with an 'input' color namespace exposing
--input-bg / --input-text / --input-border / --input-placeholder /
--input-focus-border / --input-disabled-bg, so the label color states
resolve via named utilities (text-primary, text-success, text-danger,
text-input-focus-border, text-input-placeholder) rather than arbitrary
var() values.

Net result: -161 lines of dead/duplicated SCSS, one source of truth
for label color in the TS getter.

* wire floating labels for endpoint create/edit and connect auth forms

Apply the same floating-label + input-stretch + placeholder-hide fix
(already in place on the quota forms) to all endpoint screens that use
app-form-field, so the custom-form-field ContentChild discovery works
and the labels actually render above the inputs.

Components that now import AppInputDirective so the directive attaches
to their projected <input appInput> elements:
- create-endpoint-cf-step-1 (endpoint create: name, url, client id/secret)
- edit-endpoint-step (endpoint edit: name, url, client id/secret)
- credentials-auth-form (connect dialog: username, password)
- token-endpoint (connect dialog: token)

Template inputs now carry class="w-full placeholder:!text-transparent"
so they fill the available width and the browser's native placeholder
does not bleed through the floating label (@tailwindcss/forms
otherwise overrides the placeholder color on type=number inputs).

SSO and none auth forms contain no inputs and are unchanged.

* mirror quota form validity into a signal for cd propagation

The step button state on the create/edit quota screens (org and space)
only updated after an unrelated DOM event because the
[valid]="step1.validate()" binding in AddQuotaComponent /
AddSpaceQuotaComponent / EditQuotaComponent / EditSpaceQuotaComponent
is in an OnPush host whose view-tree parent is not on the render chain
from the form component. Content inside an <app-step> is wrapped in an
<ng-template> and rendered via ngTemplateOutlet in SteppersComponent,
so markForCheck from the form walks up through the steppers, not the
page component that owns the [valid] binding. ApplicationRef.tick()
from inside statusChanges subscription throws NG0101 (recursive tick).

Fix: mirror formGroup.valid into a WritableSignal and read it from the
valid() method that the binding calls. Reading a signal inside a
template evaluation registers the consuming component as a dependent,
so Angular automatically marks it dirty when the signal changes —
works across ngTemplateOutlet and OnPush boundaries without any
manual CD scheduling.

Verified via Playwright: toggling Unlimited on required fields now
immediately flips the Create button between enabled/disabled in both
directions; also no recursive-tick errors in the console.

* wire floating labels and button state for org/space create/edit

Apply the same floating-label + signal-based validity propagation fix
that the quota forms got to the four Cloud Foundry org/space CRUD
steps: create-organization, edit-organization, create-space,
edit-space.

Template changes:
- Input elements get class="w-full placeholder:!text-transparent"
  so they stretch to fill the form and the browser's native
  placeholder doesn't bleed through the floating label.
- Edit screens' "name taken" error condition no longer calls
  validate() (which now returns form-level validity) — it reads the
  nameTaken / spaceNameTaken error from the orgName / spaceName form
  control directly.

TS changes:
- Each step imports AppInputDirective so the directive actually
  attaches to the projected <input appInput> elements and the
  custom-form-field can discover them via ContentChild.
- Each step mirrors its form's validity into a WritableSignal so the
  [valid]="step1.validate()" binding on the parent page component
  (OnPush, off the ngTemplateOutlet render chain) auto-refreshes when
  the user edits the form.
- Edit screens additionally require formGroup.dirty before the signal
  goes true, so the Update button only enables after an actual
  modification.

Refactor in AddEditSpaceStepBase: the subclass-supplied name-uniqueness
check is renamed from `validate` to `isNameUnique` so the subclass can
define its own zero-argument `validate()` method for form-level
validity without clashing with the base's validator signature. The
base's spaceNameTakenValidator now calls isNameUnique.

* convert service and cf-org-space step forms to floating-label pattern

Make the service-instance creation flow consistent with the other
form screens (quota, endpoint, org/space):

- specify-details-step, specify-user-provided-details, schema-form:
  replace <app-label>X</app-label> + <input> pairs with
  <input placeholder="X" class="w-full placeholder:!text-transparent">
  so the label floats above the input and the field stretches to fill
  the form. The Tags and User Provided Service Instance selects now
  also use the form-field's floating label (placeholder on app-select
  instead of a sibling app-label).

- create-application-step1 (shared between Create Application and
  Create Service Instance flows): replace the hand-rolled
  <div class="mb-4"><label>X *</label><app-select class="border-b ...">
  pattern with <app-form-field><app-select placeholder="X"> so the
  required asterisk, color states, and underline all come from the
  shared custom-form-field instead of ad-hoc classes. The "no orgs"
  and "no spaces" messages become <app-error> children.

- All app-select elements in these screens now default to
  [autoSelectSingleOption]="false" and get an explicit <app-option
  [value]="null">None</app-option> so a single CF/org/space doesn't
  silently auto-select.

- AppErrorComponent is now exported from @stratosui/core so consumers
  in the cloud-foundry package can import it.

* cascade-clear and suppress required error on None in cf-org-space step

The shared CF/Org/Space step (create-application-step1, also used by
the create-service-instance flow) had two UX issues after the recent
form-field conversion:

1. Clearing CF cleared Org but left Space stale. The existing service
   cascade uses withLatestFrom(org.list$) which can see pre-cleared
   data and skip the "clear space" branch. Handle the cascade
   explicitly in the template (onCfChange / onOrgChange) so clearing
   CF clears Org and Space, and clearing Org clears Space.

2. Selecting "None" on a required select immediately painted a red
   "This field is required" error, which reads as "you did something
   wrong" instead of "you're back to the starting state." When None is
   selected, reset the affected control(s) to pristine + untouched and
   force updateValueAndValidity() so the custom-form-field's
   statusChanges subscription fires and re-evaluates isInvalid (which
   is gated on `dirty`). The Next button stays disabled via the
   form's invalid state, but no error decoration is shown until the
   user actually picks something invalid.

* remove focus outline double-line and widen floating-label gap

Two visual polish fixes affecting all the screens that use
app-form-field (quota, endpoint, org/space, service instance,
schema form).

focus:outline-none:
- Browser default focus outlines were painting a blue ring around
  text-type inputs on focus, showing through as a "double line"
  against the form-field underline underneath. Number inputs already
  had outline-style: none from their own browser defaults, so only
  text inputs showed the extra border. Add focus:outline-none to every
  patched input so the form-field's own focus-state underline is the
  only focus indicator.

floating-label translate:
- The floating label was sitting 13.2px above its base, which left
  the label's bottom ~3px BELOW the input's top — they visibly
  overlapped. Change the labelClasses getter's translate from
  -translate-y-[1.1em] to -translate-y-[1.8em], giving a clean
  ~5.6px gap between the label and the input.

* bump dev version to v5.0.0-dev.6

Version bump for today's deploy to adepttech
(v5.0.0-dev.6+build.20260413.212fbae271).

Also picks up a stray emoji unicode-escape → literal-emoji change
in package.json scripts.bootstrap, harmless cosmetic diff that the
version bump script re-formatted.

* fix db URI parser to handle query parameters

The fallback parsing of cf services was neglecting the fact that the
database URI might contain query parameters. Update the regex to
capture the optional ?key=value portion, parse it into a map, and
add it to DatabaseConfig as a new QueryParams field.

Tests cover URIs both with and without query params to confirm the
regex handles both cases.

Cherry-picked from upstream PR #5196 (author: Jan-Robin Aumann).

Co-authored-by: Jan-Robin Aumann <jaumann@anynines.com>

* add optional background refresh for cnsi tokens

Introduces opt-in goroutines that proactively refresh CNSI (endpoint)
tokens in the background using stored credentials, so endpoints that
are not frequently used don't become unavailable once their refresh
token has expired.

The feature is disabled by default and must be explicitly enabled.
When enabled, background workers use the stored credentials to
re-authenticate before the tokens expire.

- New fields on the CNSI/token structs to track backgroundable state
- cnsi.go gains the scheduler + test coverage (cnsi_test.go)
- pgsql_tokens.go gains store/load helpers for credentials + tests
- main.go starts the refresh workers at startup when enabled
- Desktop helm/kubernetes plugin token handlers updated to match the
  new signatures

Cherry-picked from upstream PR #5203 (author: Jan-Robin Aumann).

Co-authored-by: Jan-Robin Aumann <jaumann@anynines.com>

* fix list header multi-filter / right-side overlap

The list header's left container (.list-component__header__left) had
`flex: 1; min-width: 0`, which told flexbox to allocate it the
leftover space after the right container, AND to allow shrinking below
its content width. When the multi-filters (cf/org/space selects)
couldn't fit in the allocated width, they would visually overflow into
the .list-component__header__right area, layering the right-side
filter labels (e.g. "Service Type") on top of the Space select.

Concretely on the services-wall view at ~1205px header width:
- __left was allocated 254px (flex: 1, with __right's content ~905px)
- __left's multi-filter content needed 385px (2 selects * 120px
  min-width + labels + gaps)
- The 131px overflow put the Space select visually underneath the
  Service Type label from __right, rendering as e.g. "Se-doSpace"
  overlapping text

Fix:
- `.list-component__header__left`: `flex: 1; min-width: 0;` →
  `flex: 0 1 auto;`. Left now starts at content size (flex-basis:
  auto), can shrink under pressure (flex-shrink: 1), but does not
  grow beyond its content (flex-grow: 0). Min-width is no longer
  forced to 0, so flexbox respects content min-size. When the total
  content doesn't fit on one row, the parent .list-component__header-
  card (which already has flex-wrap: wrap) naturally drops __right
  to a second row.
- `.list-component__header__left--multi-filters`: `flex-wrap: nowrap`
  → `flex-wrap: wrap`. When __left itself is still tight (very narrow
  viewports above the 767px mobile breakpoint), the internal filters
  can stack onto multiple rows rather than forcing __left to grow.

Verified in Playwright at 600 / 800 / 900 / 1024 / 1280 / 1400 / 1800
viewport widths: labels and selects never overlap, layout gracefully
drops rows as space shrinks. Below 768px the existing media query
switches to a column layout, unchanged.

* add copy UAA token button to page header

Ports upstream PR #5169 (author: Jan-Robin Aumann) to modern Stratos
patterns on feature/Angular-21.

Backend (clean port):
  - api/structs.go — JSON tags on TokenRecord so it can be serialised
  - session.go — new AuthTokenEnvelope struct and retrieveToken handler.
    The handler reads user_id from the signed session cookie, verifies
    the session, then returns the UAA TokenRecord for that user only.
    No client-supplied user identifier anywhere — users can only ever
    retrieve their own token.
  - main.go — GET /api/v1/auth/token route

Frontend (rewritten for Angular 21 patterns):
  - store/types/auth.types.ts — TokenData + AuthTokenEnvelope interfaces
  - store/public-api.ts — exported via `export type`
  - page-header.component.ts — uses inject(HttpClient) + inject(SnackBarService),
    firstValueFrom(), and navigator.clipboard.writeText(). No ngx-clipboard
    dependency.
  - page-header.component.html — new vpn_key button + dropdown in the
    Tailwind toggle pattern (isTokenMenuOpen), @if control flow, no mat-*

Security hardening over the upstream PR: the original kept each token in
a hidden <div>{{ token$ | async }}</div> buffer for the clipboard-copy
library to read, which left the raw tokens live in the DOM where browser
extensions could scrape them. This port skips the DOM buffer entirely —
the token is fetched on-demand when the user clicks Copy and written
straight to the clipboard API.

The /auth/token envelope is also re-fetched every time the menu opens
(via shareReplay(1) in a per-open observable) so users can't receive a
stale or expired token from a process-lifetime cache.

Co-authored-by: Jan-Robin Aumann <jaumann@anynines.com>

* support GitHub Enterprise and private repo deploy via PAT

Ports upstream PR #5195 (author: Jan-Robin Aumann) to modern Stratos
patterns on feature/Angular-21.

Before this port, Stratos' "deploy from GitHub" wizard only worked
against public repositories on github.com. Customers on GitHub
Enterprise or with private github.com repos had no path through the UI.
This port adds two optional inputs to step 2 of the deploy wizard:

  - GitHub Enterprise URL — base URL of a GHE instance (validated)
  - GitHub Access Token   — optional PAT for private repos

Backend (clean port of the cfapppush plugin):
  - types.go — GitSCMSourceInfo gains AccessToken, CloneDetails gains
    AccessToken. Typo fix: upstream used `AcccessToken` (3 c's); fixed
    here to `AccessToken` (JSON tag was already correct).
  - deploy.go — threads AccessToken from the websocket message into
    CloneDetails and into GetVCS(withAccessToken(...)).
  - vcs.go — functional-options pattern on GetVCS. Access-token URL
    rewriting moved into vcsCmd.Create itself (upstream did it at the
    call site) so it can't be bypassed. GetVCS() also returns a fresh
    struct per call to avoid concurrent callers racing on a mutated
    package-level prototype.

Frontend — modern Angular patterns:
  - deploy-application-step2.component.html — two new inputs in the
    existing form-field pattern (not mat-form-field). The access-token
    input uses type="password" + autocomplete="off" so browsers won't
    offer to save it. Error surfaces via Tailwind class, not mat-error.
  - deploy-application-step2.component.ts — new applyGithubEnterpriseAndToken()
    helper hooked into the existing valueChanges pipeline via tap().
    URL validation uses `new URL(...)` in try/catch. No-op when the
    active SCM is not GitHub (GitLab path stays untouched).
  - github-project-exists.directive.ts — renamed helper to
    getTypeAndEndpointWithAuth(), now expects 3 comma-separated parts
    (type,endpoint,token).
  - scm-base.ts, github-scm.ts, scm.service.ts — plumbing to carry
    HttpOptions with an Authorization header through every GitHub API
    call (repos, branches, commits, search).
  - @stratosui/git public_api now re-exports BaseSCM and GitHubSCM so
    consumers can call setAccessToken/setPublicApi without deep imports.
  - deploy-application-deployer.ts, deploy-application.types.ts — thread
    accessToken through the GitSCMSourceInfo envelope so it reaches the
    jetstream backend.
  - github-commits-list-config-deploy.service.ts — passes access token
    through when instantiating the SCM for the commits list.

Security notes: the PAT is a user secret of similar sensitivity to the
UAA token. It's sent over TLS, never persisted server-side, and used
only in-memory during the clone. The embedded-in-URL form (required by
git's CLI) means it briefly appears in the git process arguments —
visible to anyone with shell access inside the jetstream container.
Upstream limitation, documented here for future readers.

Co-authored-by: Jan-Robin Aumann <jaumann@anynines.com>

* support async service binding and show last-binding state

Ports upstream PR #5053 (author: Jan-Robin Aumann) to modern Stratos
patterns on feature/Angular-21. Closes the "async provisioning" UX gap
tracked in cloudfoundry/stratos#4498 and cloudfoundry-community/stratos#26.

Background: many Cloud Foundry services (postgres-as-a-service,
Blacksmith-broker-backed services) provision instances asynchronously —
the broker returns 202 immediately but the instance isn't actually
ready for minutes. The "Add Service Instance" wizard previously
pushed the user straight into a "bind to app" step, which failed for
async services because the instance wasn't ready yet. Users saw a
confusing error with no hint that the create might just be in flight.

This port makes two changes to the wizard flow and surfaces binding
state in the service list/card.

csi-mode.service.ts — when the wizard launches from the services wall
(top-down flow, likely async), override showBindApp: false so the
inline binding step is skipped. Users create the instance, see it
progress on the services wall, then bind later once it's ready.

New component ServiceInstanceLastServiceBindingComponent (standalone):
  - Reads the latest entry from IServiceBinding[] and displays its
    last_operation state via app-boolean-indicator (in-progress spinner
    vs yes/no icon) along with type + created_at.
  - Rendered both inline in the services-wall card tile and as a new
    "Last Service Binding" column in the cf-service-instances list.

New component TableCellLastServiceBindingComponent — thin wrapper that
renders "-" for user-provided service instances (no binding last_op)
and delegates to ServiceInstanceLastServiceBindingComponent otherwise.

cf-api-svc.types.ts — IServiceBinding now declares optional
last_operation: ILastOperation. The field is present on async-broker
responses today but wasn't declared in the TS type, which would have
broken the new component under strict type-checking.

Upstream changes skipped:
  - cf-shared.module.ts declarations — obsolete post-standalone
    migration. Each new component declares `standalone: true` with its
    own imports list instead.
  - add-service-instance.component.ts fixes — already present on our
    branch in cleaner form: we inject(ChangeDetectorRef) and call
    cdr.detectChanges() in ngOnInit, and apps$ uses shareReplay with
    refCount so the pipe is subscribed via async without the upstream's
    manual .subscribe() hack.
  - Karma/jasmine spec files — our branch uses vitest. Existing tests
    cover the surrounding code; the new components are small enough
    that coverage comes from the e2e wizard flow.
  - tsconfig.json / package.json reformat — purely cosmetic in upstream.
  - Debug console.log(this.serviceInstance) that upstream left in
    ngOnInit — removed.
  - Boxed Boolean type with // tslint:disable-next-line:ban-types —
    replaced with primitive boolean.

Co-authored-by: Jan-Robin Aumann <jaumann@anynines.com>

* add Go unit tests for retrieveToken and vcs URL rewriting

Covers the two pieces of novel backend logic added in the recent port
batch (PR 5169 + PR 5195).

auth_test.go:
  - TestRetrieveToken — success case. Mirrors TestVerifySession's mock
    plumbing (setupHTTPTest, sqlmock, InitStratosAuthService) to stub a
    signed-session user_id/exp and verify the handler returns an
    AuthTokenEnvelope containing the decrypted TokenRecord.
  - TestRetrieveTokenNoSessionDate — error path. Omits "exp" from the
    session so GetSessionInt64Value fails, and verifies the handler
    writes an error envelope (status:"error", data:null) rather than
    propagating the error to echo.

vcs.go:
  - Extracted the access-token URL rewrite logic from Create() into
    vcsCmd.repoWithToken(repo). Create() now delegates to it. Pure
    refactor — no behavioural change — done so the rewrite can be
    unit-tested without shelling out to a real git binary.

vcs_test.go (new file, six tests):
  - TestRepoWithToken_NoTokenReturnsURLUnchanged — no-op when token empty
  - TestRepoWithToken_EmbedsTokenAsBasicAuth — PAT injected as
    x-access-token basic-auth userinfo
  - TestRepoWithToken_InvalidURLReturnsError — malformed URL surfaces
    a wrapped parse error
  - TestRepoWithToken_SpecialCharactersInTokenEscaped — tokens
    containing @, :, / must be percent-encoded so they don't prematurely
    terminate the userinfo section. This is the class of bug that would
    silently point git at the wrong host.
  - TestGetVCS_ReturnsFreshCopyNotPrototype — confirms GetVCS() returns
    a copy of the package-level vcsGit prototype rather than the
    prototype itself, so concurrent callers can't race on a mutated
    accessToken field. Directly exercises the concurrency fix that
    deviated from upstream PR #5195's GetVCS implementation.
  - TestGetVCS_WithAccessTokenOption — confirms the functional-options
    pattern actually applies the token to the returned struct and
    preserves the prototype's other fields.

* FWT-917 instrument CfOrgSpaceDataService for diagnostic capture

Adds a dev-build-only event log to CfOrgSpaceDataService so the
CF→Org→Space pre-fill inconsistency tracked in FWT-917 can be
diagnosed with a Playwright harness that reads events via
window.__cfOsDebug.snapshot().

cf-org-space-debug.ts (new) — small CfOrgSpaceDebug class with a
bounded ring buffer (500 events/instance), performance.now()
timestamps, and a [CFOS #N] prefixed console.log for manual
devtools use. createCfOrgSpaceDebug() factory registers each
instance with a global aggregator on window.__cfOsDebug that
returns a time-sorted merge across every instance — necessary
because CfOrgSpaceDataService is component-scoped (each wizard
gets its own instance), and H1 ("no cross-route handoff") is the
leading hypothesis. Gated on !environment.production at every
entry point so prod builds are no-ops and globalThis is never
touched.

cf-org-space-service.service.ts — log calls at the 10 event
points needed to discriminate the six hypotheses:
  - service:construct          (H1 — per-instance lifecycle)
  - cf:list-emit               (upstream CF endpoint emissions)
  - cf:select-change           (BehaviorSubject value changes)
  - org:list-emit              (filtered org list emissions)
  - org:select-change
  - space:list-emit
  - space:select-change
  - allOrgs:entities-emit      (H2/H5 — pagination race timing)
  - initialValues:resolved     (H4 — stale filter inputs)
  - autoSelector:setup         (H1 — confirms enableAutoSelectors was called)
  - autoSelector:cf-pre-filter (H3 — happy-path emission swallowed by filter)
  - autoSelector:cf-cascade-fire
  - autoSelector:org-pre-filter
  - autoSelector:org-cascade-fire

The cf-pre-filter and org-pre-filter taps sit BEFORE their
respective filter() operators so the diagnostic log captures
emissions that the filter drops — that's the signature pattern
for hypothesis H3 (filter(cf !== initialCf) swallows the first
real emission when the persisted initial CF matches reality).

allOrgs.entities$ is wrapped with a non-invasive tap via object
spread — subscribers to this.allOrgs.entities$ see the tap,
this.allOrgs.pagination$ bypasses it, and the original
getPaginationObservables result is not mutated.

Zero production impact verified: the debug class short-circuits
log/snapshot to return early when environment.production is true,
and the factory returns an instance without ever touching
globalThis in prod.

Existing cf-org-space-service.service.spec.ts (14 tests) passes
unchanged — the instrumentation is purely additive.

* FWT-917 extend instrumentation to cover quota and breadcrumb paths

The original instrumentation in 2a670a921e covered CfOrgSpaceDataService,
which powers the picker dropdowns in app/service wizards. But the user
reports the same symptom on quota wizards (org quota create/edit, space
quota), and a grep confirms quota wizards do NOT use CfOrgSpaceDataService
at all — they pull cfGuid from the route via ActiveRouteCfOrgSpace and
display CF/Org/Space context via CfOrgSpaceLabelService (the breadcrumb
service). Three additional instrumentation surfaces are needed …
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