Open
Conversation
This reverts commit 074f590.
…ly optional and cover with tests
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 …
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Checklist: