Bitween redesign: the new admin UI on the r10.0 line - #233
Conversation
Point the SPA's api at a new httpClient (src/api/http/) that calls /bitween/api instead of the mock: login, Microsoft login, logout and getSession are live; un-wired domains reject with NotWiredError. Drop the demo persona switcher.
List/get/create/update/delete plus API credential add/revoke, all live-tested against the local DB. Credential writes always resend the full set since the backend replaces it wholesale; masked values are preserved server-side by name, so add/revoke/rename are all safe. Also fixes two request-layer bugs found while wiring: generatekey returns a bare text/plain key (not JSON), and date formatters crashed on fields the backend doesn't return (now render as an em dash).
Every save mutation reset the dirty-draft state before its detail-query invalidation had actually refetched, so the re-sync effect read stale cached data and then locked itself out of re-syncing once the fresh data arrived. The save had gone through and the DB was correct, but the Unsaved bar never cleared and the form kept showing pre-save values. Fix: await the detail invalidation before resetting the draft, everywhere this pattern is used (partners, retry policies, api/bus gateways, integrations, information types, work groups, notifiers).
Also fixes Name/Code edits silently no-oping on update — SetProperties() skips private-setter fields, so explicit SetName()/SetCode() calls are needed before it runs.
Existing rows keep code = NULL instead of a name-derived value, avoiding unique-index collisions on less-controlled data outside local dev.
WorkGroupModel now carries ProcessorNodeCount (from RabbitMQ's live consumer stats) instead of leaving the UI's "Consumers" column faked. Search.cs also reads WorkGroup straight from the DB instead of IInfolinkCache, which only invalidates via a broadcast back to itself over RabbitMQ - a create/edit/delete could stay invisible until that round-trip landed or the cache's TTL expired.
Completes Batch 1 (reference entities). Retry policies convert the backend's millisecond-based DelayStrategy to/from the UI's seconds-based RetryDelay. RetryPolicyListRow now carries groupCount instead of a full groups array, matching what the list endpoint actually returns instead of fabricating placeholder group entries.
Also fixes a cache-broadcast failure that could 500 a successful subscription write, and stops React Query from retrying permanently unwired endpoints (was stalling unrelated pages for ~7s).
Also fixes ApiGateway delete not cascading to partner attachments (FK violation), and works around a composite-key update bug in the gateway attachment endpoint via remove-then-add.
…h failures BulkRetry.cs threw on document-only exchanges with no subscription. adapters.ts let one failing legacy adapter blank out the whole catalog via Promise.all.
Gateway/manual exchanges had no InputName (no filename passed to XchangeFile), so the UI showed "no document" even when content existed. Wire the drawer to fetch stage content by storage key instead of relying on filenames, and ignore local Playwright artifacts.
…SON path MatchExpressionEditor and its callers discarded a promoted property's path and submitted its display name as the condition instead, so any saved route/filter matched against the wrong field.
…nge paths Several places that build an Xchange (scheduled receivers, aggregation, manual create, retry-with-reset, the legacy direct-update endpoint) never loaded the Global Values set before resolving Mapper/Handler/ Validator properties, so tokens were silently left unresolved and later crashed whatever adapter tried to use them as a real value. Centralize the lookup in XchangeService instead of relying on every caller to remember it, and fix the retry-with-reset constructor which had no resolution step at all. Also fixes the reference-hint display under adapter text fields silently disappearing when a global value's key contains a space.
…scoped exchange lookup Xchange.PartnerId (not Subscription.PartnerId) is the real partner source for gateway/bus-routed traffic, with a fallback for legacy rows predating the column. Info type and global value set detail pages now compute their usage panels from real subscription/trail data instead of stubs.
Spaces end up literal in the RabbitMQ routing key/queue binding, breaking the dot/hyphen-delimited naming convention used elsewhere. Blocked both server-side (create/update validation) and client-side (stripped on input).
Cron triggers had no explicit timezone (silently followed the server's local time), while Schedule.Next() assumed UTC — the two disagreed whenever the server wasn't running in UTC. Upgraded to SimplyWorks.Scheduler.* 8.1.7, which pins triggers to UTC explicitly, and matched Schedule.Next() to agree.
Schedule hour/minute are UTC; editors had to do the mental conversion themselves. Labels the inputs as UTC and shows a live "X your local time" preview while editing.
…d test-partner adapter properties never populated
Live Preview only ran client-side generateScriban() from the mock-only
prototype phase; the real POST /mappers preview endpoint already existed
server-side but was never wired into the client. Wire it up.
Separately, the test-partner selector derived adapterProperties from the
partner list endpoint, which never carries them (always {}) - only the
per-partner detail does. Fetch the selected partner's properties directly
so the Partner-mode datalist actually suggests real keys.
… spaces, or leading digits
scribanGenerator spliced partner property keys and global value-set ids as
bare Scriban identifiers after `?.`. Hyphens get tokenized as subtraction
and a leading digit is a hard parse error, so any id/key that wasn't a
single plain word broke the generated template - both in Live Preview and
for real, since the same template runs live exchanges.
Index by quoted string instead (works for any content) with `?? {}` at
each hop, since `?.` only guards one hop and not a chained indexer after
it. scribanParser now recognizes both the new and the old shape so
already-saved templates still round-trip.
Both derived usedByCount/integrations from data never populated by the API. Now cross-referenced against /subscriptions' WorkGroupId/RetryPolicyId.
Create had no validation and Update silently dropped BusMessageName changes entirely. Now validated (no spaces) and persisted on update; UI strips whitespace as you type.
FindMatchingGroup filtered matchers then called .Any(), which is always false on an empty list — so a "match everything" group (the documented meaning of zero conditions) silently blocked every retry.
feat: team management and role-based access control
Test gained a BitweenDbContext parameter and the three dry-run call sites were never updated, so the whole project failed to build. Handlers also resolve permissions from the database now, which no test had an identity for — they run as break-glass superuser.
These handlers never had a guard of any kind, so the EnsureAccess sweep didn't reach them: any signed-in account could add, rename or delete a work group.
notifiers.delete has no endpoint behind it, and retry-policies.operate stopped being used when Run now moved to exchanges.operate — which also left the button visible to roles the endpoint then refused.
Reads were unguarded, so any signed-in account could list partners, exchanges, documents and gateways straight from the API. Lookup mode stays open — it returns only id/name pairs, which pickers across the app depend on — and reads the dashboard shares accept either permission. One commit rather than two: the guards on their own break the four "Used by" counts, because a refused secondary read took the whole page down with it.
The Settings table is the single source of truth for the 22 editable settings; configuration seeds each key once on first boot and is ignored afterwards. Secrets are encrypted at rest with AES-GCM. Rebex adapters are now always registered so the license key takes effect without a restart.
feat: move live-changeable settings into the database
Nothing has imported it since the last wiring batch. The ApiClient interface stays as the contract the UI is written against.
Thirteen configuration values that are read once at startup now appear on the settings page as read-only, or as set/not-set where the value shouldn't leave the server. Only editable settings get a database row. The retry poll schedule is editable and re-schedules the Quartz trigger on save.
Create.cs keeps both sides: SSO-only accounts get no password, roles still come from RoleIds. Search.cs had RetryPolicyId added on both sides.
The flag arrived with the r8 merge, where only the backend enforced it — the new sign-in page still offered a form the Login handler would reject. It's now a setting in the catalog, and the page hides the form when it's on.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (5)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: simplify9/coderabbit/.coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 3998894 | Triggered | Generic Password | 7023c12 | SW.Bitween.Web/ClientApp/e2e/helpers.ts | View secret |
| 35205739 | Triggered | Generic Password | 7023c12 | SW.Bitween.Web/ClientApp/e2e/helpers.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
The value was copied out of appsettings.Local.json, which is gitignored for exactly that reason. Nothing in the test depends on its contents.
|
Superseded: the branch was rebuilt as 10 thematic commits, and a Rebex license key that had been committed as test data is no longer present in any commit. Replacement PR to follow. |
The redesigned Bitween admin UI, served by the backend, plus the backend work it needed. This opens the
r10.0line; the .NET 10 upgrade itself is a separate PR on top of this one, deliberately kept apart so a runtime failure can't be ambiguous between "new UI" and "new framework".How to read 357 files
The line count is dominated by generated and new-file content, not by changes to review:
SW.Bitween.Web/ClientApp— the new SPASW.Bitween.Api— the actual backend changeThe 50 commits went in as labelled batches — partners, information types, global values, work groups, retry policies, notifiers, integrations, operate pages, team/RBAC, settings. Reviewing commit by commit is far easier than file by file.
Merge resolutions worth a look
releases/r10.0was merged into this branch so the conflicts are resolved here, in the open, rather than inside the merge commit that lands it. Three needed a decision:Accounts/Create.cs— r8'sDisableEmailPasswordLoginand this branch's RBAC rewrite both changed the same lines. Both kept: roles come fromRoleIds, and a Microsoft-only instance creates accounts with no password.RuleFor(i => i.Role).NotNull()was dropped, sinceRolebecame optional in favour ofRoleIds.Subscriptions/Search.cs— merged without a conflict but did not compile:RetryPolicyIdwas added on both sides, so git kept both (CS1912). Deduped.SW.Bitween.Api.csproj— whitespace only.One regression the merge caused, fixed here
DisableEmailPasswordLogin(r8, #230) was enforced by the backend but unknown to the new sign-in page, which still offered a form theLoginhandler would reject. It's now a proper setting in the catalog, and the page hides the form when it's on — including the case where it's on with no MSAL configured, which would otherwise render an empty card.Verification
dotnet build SW.Bitween.slntsc -b/yarn lint/yarn buildThe Playwright failure is
exchanges.spec.ts:17— pre-existing data drift, where the fixture exchange has aged past page 1 of 25. It fails identically before this merge.Integration tests are Testcontainers-based and were compiled but not run (they need Docker).
Follow-up PRs, not in scope here
dotnet-version,major-version, and the hardcodedreleases/r8.0job gate, which currently makes everyreleases/r10.0push a no-op). Dependabot has already written part of it in chore(deps): bump dotnet/sdk from 8.0 to 10.0 #197 and chore(deps): bump dotnet/aspnet from 8.0 to 10.0 #199, both aimed atreleases/r8.0where they'd break the image — retarget rather than redo. Bump Npgsql.EntityFrameworkCore.PostgreSQL from 8.0.11 to 9.0.4 #220 and Bump Pomelo.EntityFrameworkCore.MySql from 8.0.3 to 9.0.0 #221 (EF 9 providers) are in the same boat..github/dependabot.ymlhardcodestarget-branch: releases/r8.0in all three ecosystems, so Dependabot will never seereleases/r10.0.🤖 Generated with Claude Code