Skip to content

Bitween redesign: the new admin UI on the r10.0 line - #233

Closed
hamzahalq wants to merge 51 commits into
releases/r10.0from
v2
Closed

Bitween redesign: the new admin UI on the r10.0 line#233
hamzahalq wants to merge 51 commits into
releases/r10.0from
v2

Conversation

@hamzahalq

Copy link
Copy Markdown
Contributor

The redesigned Bitween admin UI, served by the backend, plus the backend work it needed. This opens the r10.0 line; 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:

files added
SW.Bitween.Web/ClientApp — the new SPA 179 +28,385
EF migration + Designer snapshots (3 providers) 32 +26,209
SW.Bitween.Api — the actual backend change 121 +2,340 / −205
Sdk, tests, charts, Dockerfile, Startup 27 +828

The 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.0 was 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's DisableEmailPasswordLogin and this branch's RBAC rewrite both changed the same lines. Both kept: roles come from RoleIds, and a Microsoft-only instance creates accounts with no password. RuleFor(i => i.Role).NotNull() was dropped, since Role became optional in favour of RoleIds.
  • Subscriptions/Search.cs — merged without a conflict but did not compile: RetryPolicyId was 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 the Login handler 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

check result
dotnet build SW.Bitween.sln 0 errors
C# unit tests 179 passed, 7 skipped
tsc -b / yarn lint / yarn build clean; 0 lint errors
vitest 58 passed
Playwright 42 passed, 1 failed

The 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

  1. .NET 10 upgrade — 13 TFMs, Dockerfile bases, and the CI inputs (dotnet-version, major-version, and the hardcoded releases/r8.0 job gate, which currently makes every releases/r10.0 push 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 at releases/r8.0 where 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.
  2. .github/dependabot.yml hardcodes target-branch: releases/r8.0 in all three ecosystems, so Dependabot will never see releases/r10.0.

🤖 Generated with Claude Code

hamzahalq added 30 commits July 16, 2026 10:56
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.
hamzahalq and others added 20 commits July 26, 2026 13:40
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.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (5)
  • ^main$
  • ^master$
  • ^staging$
  • ^development$
  • ^gigstaging$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: simplify9/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b6eb005-cce6-4870-b462-ddce74620b98

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitguardian

gitguardian Bot commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
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
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. 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


🦉 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.
@hamzahalq

Copy link
Copy Markdown
Contributor Author

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.

@hamzahalq hamzahalq closed this Jul 29, 2026
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