xrmverse
Documentation menu

Web admin

← xrmverse documentation

The host serves a web admin (React single-page app, built from ui/) at the root of the same host and port, e.g. http://localhost:5001/ for the first organization of xrmd server init (http://localhost:5000/ with the repository compose file). The Docker image builds it into wwwroot (docker/Dockerfile); outside Docker the host serves the directory in Ui:RootPath (default wwwroot under the content root) and, if it holds no build, a placeholder page. The shell uses only the Web API and the Admin API: every write goes through the Core, so the pipeline, plug-ins and security apply as for any other client. The anonymous GET /ui/config.json publishes the public values the shell boots from (authMode, audience, authority, clientId, scope, apiVersion); never the signing key.

Who can use it. Users holding the System Administrator or System Customizer role (directly or through an owner team; recognised by the role template, not by name). Anyone else sees an "Access denied" page naming the two roles. The Environment section (health, doctor, version, and the forms' auto-save switch, organization.isautosaveenabled) and the cascade solution delete use the Admin API and are shown only to System Administrators; a Customizer who opens /manage/environment sees "Requires System Administrator". Sections (all under /manage/*, because /admin/* is the Admin API): Environment, Users, Teams, Security roles, Business units, Tables (columns, relationships, records), Solutions (import, export, delete), Plug-ins (assemblies, steps, trace log), Async operations, Audit, Duplicate detection, Field security and Hierarchy security (System Administrators only) and About (the trademark notice, where the third-party notices are, and a link to SECURITY.md; every signed-in admin or customizer sees it). /apps and /main.aspx open the model-driven apps of imported solutions, for any signed-in user: see Model-driven apps. Server paths (/api, /admin, /mcp, /XRMServices, /metrics, /healthz, /ui/config.json, /WebResources, /Image) are never answered with the shell.

Look and feel

The web admin, the sign-in page and the model-driven apps share one theme: the xrmverse Indigo palette (ui/src/shell/theme.ts) on Fluent UI v9, our own logo and name in a dark header bar, and the system font stack (the brand font Sora is not shipped yet, #11). Each admin page has a title on its own line with its main action on the right; pages below a list (a table, a record) show a breadcrumb of the parent pages above the title. The section navigation highlights the current section; on narrow windows (600 px and less) it collapses behind a Menu button in the header and closes when a section is chosen.

Signing in

  • Development (AUTH_MODE=Development). The sign-in page asks for the signing key (AUTH_DEV_SIGNING_KEY), an object id (default: the seed administrator 00000000-0000-0000-0000-000000000001) and a display name. The browser mints the same HS256 token as docker/dev-token.sh with WebCrypto; no server endpoint mints tokens. The key is kept in sessionStorage only (gone when the tab closes), never in localStorage. WebCrypto needs a secure context: open the shell over HTTPS or on localhost; elsewhere the page says WebCrypto is unavailable.
  • Microsoft Entra ID (AUTH_MODE=Entra). The shell signs in with MSAL.js (redirect flow) and needs its own SPA app registration, separate from the API's:
    1. In the tenant, register an application with a Single-page application platform and the redirect URI of the shell: the origin of the page plus / (e.g. http://localhost:5000/, or your HTTPS URL).
    2. Under API permissions add the scope exposed by the API registration (the one whose Application ID URI is AUTH_AUDIENCE).
    3. Configure the server with the SPA's client id and that scope: Ui:Entra:ClientId and Ui:Entra:Scope (UI_ENTRA_CLIENT_ID and UI_ENTRA_SCOPE in .env, which the compose files map to Ui__Entra__ClientId and Ui__Entra__Scope; empty by default). The authority is derived from Auth:TenantId (https://login.microsoftonline.com/{tenant}/v2.0). If any of the three is missing, the shell shows a configuration error naming the keys instead of a sign-in button.

Errors from the server are shown with the Dataverse error code and message. Lists page 50 records at a time (Prefer: odata.maxpagesize=50, @odata.nextLink).

Audit

The Audit section (/manage/audit, see Auditing):

  • switches auditing of the organization on or off and sets the retention in days or forever (System Administrator only; others see the settings read-only);
  • lists the audit log, newest first, filtered by table, operation and user, and opens an entry in a drawer with the old and new value of each column. Reading it needs prvReadAuditSummary; without it the page shows the server's 403.

In Tables, a table has an "Audit this table" switch and each column an "Audit" switch; a setting the server has fixed is disabled. A record form of an audited table shows its Audit history, read from audits and RetrieveAuditDetails (it needs prvReadAuditSummary). The web admin does not delete audit data: use xrmd audit delete or the messages.

Field security

The Field security section (/manage/security/fieldprofiles, see Field-level security) is shown only to System Administrators, the only role that holds the profile privileges. It lists the profiles, creates and deletes them, and edits a profile's permissions in a matrix of secured columns (display label, logical name below) against read, create and update; each toggle writes the fieldpermission row. It gives a profile to users and teams and takes it back. The System Administrator profile is shown read-only.

In Tables, each column has a Secured switch, disabled when the column cannot be secured. A record form marks secured columns with a lock and shows "No access" for a secured column the server left out of the record.

Hierarchy security

The Hierarchy security section (/manage/security/hierarchy, see Hierarchy security) is shown only to System Administrators. It switches the manager hierarchy model on or off, sets how many levels of reports a manager reaches (1 to 100) and lists the excluded tables, which you add by logical name and include again one by one. Saving writes the settings through the Admin API; the server judges every value and its refusal is shown as it is.

Access team templates

The Access team templates section (/manage/security/accesstemplates, see Access teams) lists the templates with their table, name and rights, one checkbox per right (Read, Write, Append, AppendTo, Delete, Share, Assign). It creates a template for a table, changes its rights (each click is one update, and only teams created afterwards get the new rights) and deletes it. Delete asks for confirmation first, because the access teams made from the template are deleted too. A Auto-create access teams switch for the chosen table is on the page (a template can be created only for a table that is switched on) and on the table's own page in Tables. The page is not limited to System Administrators in the menu, but the server answers with its 403 (shown as it is) to a user without the teamtemplate privileges, which in the standard roles only System Administrators hold. The web admin does not add users to a record's team: use AddUserToRecordTeam (SDK, Web API) or xrmd access-team add.

Change tracking

On a table's page in Tables, a Track changes switch turns change tracking on for the table (see Change tracking). Change tracking cannot be turned off again, so the switch asks for confirmation first and, once on, stays on and disabled with a hint. It is disabled with a hint too when the table cannot be tracked (an intersect table, audit and the other tables the page lists as ineligible); the server's refusal of any other case is shown as it is. The retention (expirechangetrackingindays) is not on a page: use xrmd change-tracking retention or the organization row.

Duplicate detection

The Duplicate detection page (/manage/duplicates, open to System Administrators and System Customizers) manages duplicate detection (see Duplicate detection). At the top, four switches set the organization's columns: duplicate detection, detect when a row is created or updated, detect on import and detect on offline sync; the last two carry a hint that they are stored and nothing applies them. Below, the table lists the rules with their base table, matching table and status (Unpublished, Publishing or Published). New rule and Edit open a dialog with the name, description, base and matching table (only tables with duplicate detection on are offered; the tables cannot change on an existing rule), the two checkboxes (compare text with case, ignore inactive rows of the matching table) and the conditions: a base column, a matching column, an operator (exact match, same first or last characters with a count, same date, same date and time, exact match of option value) and ignore blank values. Publish starts the system job and the page shows its progress (waiting, running, ended) until the rule is Published or the job's message explains the failure; Unpublish and Delete ask for confirmation first. A published rule cannot be edited: unpublish it first.

On a table's page in Tables, a Duplicate detection switch sets IsDuplicateDetectionEnabled (turning it off unpublishes the rules of the table, as the hint says). The server judges every value and its message is shown as it is. A rule is saved in several writes, so when one is refused the dialog shows the server's text and a note that the rule may be partly saved; it then lists the conditions the server holds, and you fix the problem and save again. Running RetrieveDuplicates and BulkDetectDuplicates is not on a page: use xrmd duplicate-detection find and bulk, or the Web API.

Activities and notes

In Tables (see Activities and notes):

  • New table has an Activity table switch. When it is on, the primary name column is Subject (shown and disabled) and the ownership is user (disabled); the table is created as a custom activity. This cannot change later.
  • A table's page has a Notes switch and an Activities switch (HasNotes, HasActivities). Each asks for confirmation before it saves, because neither can be turned off again; once on, the switch stays on and disabled with a note. An activity table shows "This is an activity table" and no Activities switch. When the server does not report a setting, no switch is shown; a refusal of the server is shown as it is.
  • A record form of an activity shows its party lists (from, to, requiredattendees, …) read-only, read through $expand={activity}_activity_parties: each participant as "Name (table)", with the table's logical name; a participant with no name as the table and id; an address alone as the address; "None" for an empty list. Party lists are never sent on create or update, and are not offered as list columns. Change participants with the SDK or the Web API.
  • A note's form hides documentbody. Timelines, activity forms and a note editor are not part of the web admin.

Currencies

The Currencies page (/manage/currencies, System Administrators only) lists the currencies with code, name, symbol, precision, exchange rate and state; the base currency has a Base badge. New currency opens a dialog for a System currency (ISO 4217 code) or a Custom one (three capital letters), with name, symbol, precision and rate. A rate is edited in the row and saved with Save (the base currency's rate is fixed at 1, with a hint). Deactivate asks for confirmation; Activate does not; both are disabled for the base currency. Pricing precision sets organization.pricingdecimalprecision (0 to 4). The page says that changing a rate does not change existing records until a save changes their money or currency. A column with no value (a NULL rate, for example) is shown empty. A refusal from the server is shown in a message bar as it is, inside the dialog for a new currency (the dialog stays open). In a record form the currency is a normal lookup; exchangerate and the _base columns are not offered, because the server sets them. See Currencies.

Deleting a solution in the UI

The Solutions page offers the same two options as the CLI (see Deleting a solution):

  • Delete solution removes only the solution container, as in Dataverse. Its components stay in the system, tracked by the Default solution.
  • Delete solution and its components is xrmverse-only (System Administrator only): it also drops the components (tables and their data) that no other solution holds. It goes through the Admin API, requires typing the solution's unique name, and cannot be undone.

Known limitations

Anything the server cannot do faithfully is reported, never emulated:

  • Business unit disable. The UI sends isdisabled like Dataverse, but the server's businessunit table has no isdisabled column yet, so the server answers with an explicit error, which the UI shows.
  • Cascade delete preview. The confirmation dialog lists the solution's components; which of them are kept (system components, components held by another solution) is only known after the delete, because the server has no dry run. The result then lists deleted and kept components with the reason.
  • Plug-in assemblies are registered with the CLI (xrmd plugin register <dll> [--solution <name>]): the CLI inspects the DLL on your machine. The Plug-ins page browses assemblies, types and steps, and enables or disables steps; it lists plug-in steps only, so a webhook and its steps are managed with xrmd webhook, the SDK or the Web API (see Webhooks). Its Custom APIs tab lists the custom APIs read-only (binding, kind, private, plug-in type, parameters and properties); it does not create or change them: use xrmd customapi create, the SDK or the Web API, or import a solution (see Custom APIs).
  • New schema names. When you create a table or column the UI suggests <default publisher prefix>_<PascalCase display name> (for example xv_ProjectTask); you can edit it. The logical name is the lowercased schema name. If the default publisher has no prefix, no name is suggested.
  • Server gaps the UI works around. EntityDefinitions does not expand relationships, so the UI reads RelationshipDefinitions to find the navigation property of a lookup. A polymorphic lookup accepts one navigation name per attribute on the server; the UI binds with that name and never guesses one.

Development loop

pnpm -C ui install
pnpm -C ui dev        # http://localhost:5173, proxies /api, /admin and /ui/config.json to http://localhost:5000
pnpm -C ui test       # unit tests (Vitest)
pnpm -C ui typecheck
pnpm -C ui build      # writes ui/dist
pnpm -C ui e2e        # Playwright scenarios against a real host and SQL Server

Run the .NET host on port 5000 first (dotnet run --project src/Xrmd.Host with the configuration of docker/.env, or the Docker stack). The host is built without the Microsoft SDK and exits with code 3 until it finds it: install it once with xrmd sdk install (into ~/.xrmd/sdk), or, as a developer, point XRMD_SDK_DIR at the restored package, ~/.nuget/packages/microsoft.powerplatform.dataverse.client/1.2.27/lib/net10.0 (NUGET_PACKAGES if you set it). pnpm -C ui e2e sets XRMD_SDK_DIR to that folder itself and stops with a message when it is missing (run dotnet restore xrmverse.sln). pnpm -C ui e2e starts its own SQL Server with docker run, builds the UI, the host and the fixture solution, and runs the host in development mode on a free port; it needs Docker, the .NET SDK, zip and a Chromium (XRMD_E2E_CHROMIUM, else /opt/pw-browsers/chromium, else Playwright's own). See ui/README.md. The manual acceptance checklist is docs/superpowers/acceptance/web-admin-checklist.md.