Skip to content

Data Explorer landing: API call by default, roles/keys that can read the table - #545

Open
nicdavidson wants to merge 23 commits into
developfrom
feat/data-explorer-landing
Open

nicdavidson wants to merge 23 commits into
developfrom
feat/data-explorer-landing

Conversation

@nicdavidson

Copy link
Copy Markdown
Contributor

Makes Data Explorer a friendlier landing page: show the API call for what you see, and get the user to a working API key from there.

Stacked on #537. Merge #537 first. Until then this diff also shows #537's commits. The Data Explorer work is the 4 data-explorer commits.

Changes

  • API call bar on by default. The REST call that matches the current view shows as soon as a table opens.
  • Roles that can read this table. On table load, Data Explorer lists active roles with API GET access to the table: this service or all services, and component *, _table/*, _table/<t> or _table/<t>/*. Roles with keys come first.
    • A role with a key shows the key (masked in presentation mode), Copy curl with the X-DreamFactory-API-Key header, and Edit role.
    • A role without a key offers Create key, which binds a new API key to that role.
    • Several roles: pick one from a small dropdown.
  • No role has access: a short explanation of keys and roles, links to Roles and API keys, and Create read-only key for . That creates a role with GET on that table only, plus a key. Clicking again reuses it instead of failing with a batch error.
  • Filter fix. DF types foreign keys as reference, so an int FK got text operators (contains became like '%2%') and the server rejected it. Operators now check the DB type too.
  • Error recovery. An error hid the filter row and Retry resent the same filter. The error state now offers Clear filters.
  • Notes

    • Row-level filters on a role's access row are ignored when deciding "can read". A filtered role still shows as having access.
    • The role lookup loads all roles with their access rows. Fine for typical installs (marked ponytail: in code).

    Tested

    • Unit tests: 34 suites, 307 tests pass.
    • Browser (df-dev, logistics sqlsrv): delivery_center lists all 4 roles with access and keeps the key after a reload. driver lists 2 keyless roles. Filtering customer_id = 2 returns 6 rows.
    • The generated key returns 200 on delivery_center, 401 on another table, and 401 on POST.

nicdavidson and others added 23 commits October 5, 2026 12:59
Screensharing the admin UI puts live API keys on someone else's monitor.
One toggle in the top bar now covers every credential the UI renders, with
a per-field eye to reveal one on demand.

Display and clipboard are deliberately separate paths: df-try-it renders
displaySnippet and copies snippet, df-mcp-connect renders
maskIfPresenting(x) and copies x. A masked curl command still pastes into
Postman and works.

The value leaves the DOM rather than being blurred — a CSS blur stays
selectable and recoverable from a screenshot.

Service-config secrets key off the field name, not the schema type:
oidc.client_secret ships as `text` and mcp.oauth_client_secret as
`string`, so both render as plain visible inputs. `_id` is excluded so
client_id stays readable during OAuth setup.

This is not a security boundary — the key still arrives in the API
response and is visible in devtools. It addresses screenshare exposure.

Covered by 34 unit tests (registered in jest.config.ci.js) and
e2e/presentation-mode.spec.ts, which asserts the clipboard still carries
the exact unmasked command and sweeps the leaking routes, failing loudly
if a swept route has no credential left to hide.
…s table

overflow-wrap: anywhere let the auto-layout table shrink the API key
column to a single character, stacking the 64-char key vertically.
Show it on one line, capped at 180px with an ellipsis instead.
Shared df-select-search header + dfSearch pipe. Role-Based Access service
and component pickers get type-to-search; the component picker also gets
All/Schema/Table/Function/Procedure toggles. Wildcards stay selectable
while filtering. Users/Admins app+role autocompletes now narrow as you
type; Event Scripts pickers and the API Docs operation nav are searchable.
Saved payloads are unchanged.

Closes #534
… wildcards

Replace the Schema/Table/Function/Procedure toggles with a compact Type
select derived from each component's first path segment, so file and
system services get types too. Within a type only that type's own
wildcards are kept; the query matches the part after the prefix.
Keys typed on a focused-but-closed select, or before the panel finished
opening, hit mat-select's jump-to-option typeahead instead of filtering.
The request builder's {table}/{field}/relation token pickers become
mat-selects with the shared search, as do the filter builder's Field
picker and Try-it's Identity picker. Register the dfSearch spec in the
CI jest config.
requireSelection keeps typed text out of the form control until an
option is picked, so the filter read a stale value and never narrowed.
/system/event is exempt from the case interceptor, so event parameters
arrive as table_name / procedure_name / function_name. The component only
matched the camelCase keys, so the name picker never appeared and a
script could not target one table (e.g. a pre_process on driver).
A service with no stored functions/procedures sends parameter: null, which
hid the picker and left the script name with a literal {function_name}.
Derive the kind from the event's placeholder instead, show an empty
picker, and let a typed name be used (script ahead of creating it).
Changing the admin password blacklists the current session token. If the
dead token stays in the cookie, every page load sends it with the bootstrap
GET /system/environment and DF answers 403 "The token has been blacklisted:
Session terminated. Please re-login". The error interceptor only cleared
the token on 401, and returned early for silent requests (the environment
call is silent), so the login page never rendered until the user cleared
browser storage by hand.

- errorInterceptor: a 401/403 whose message says the token was blacklisted,
  terminated or expired, on a request that carried a session token, now
  clears the token, routes to login, and retries the request once without
  the token. This runs before the silent opt-out, so bootstrap recovers.
- normalizeError: such 403s normalize to kind auth instead of forbidden.
- DfPasswordService.updatePassword: only store a session token the server
  actually returned.
- Tests cover the bootstrap recovery, the single retry, plain 403s, plain
  401s, and staying put on the login page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ckers-presentation-mode

Brings the dead-session recovery into the branch df-dev runs. Conflict in
error.interceptor.ts resolved by keeping the trial-lock branch first (it
must win over the dead-session check, and still applies to silent
requests), then the dead-session clear-and-retry, then the existing
severity routing. error.interceptor.spec.ts now holds both the trial-lock
and the dead-session suites. dist rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… read-only key

Data Explorer is becoming the landing page, so the matching REST call is
visible from the start. Under it, a prompt explains that an app calls the
URL with an API key whose role sets its reach, and one click creates a
role that can only GET this table plus an API key bound to it. The key
honors presentation mode and copies as a curl command.
… filters, reuse starter keys

- Foreign keys arrive as type reference, so an int FK got text operators
  (contains -> like '%2%') and the server rejected it. Operators now check
  the db type as well.
- An error hid the filter row and Retry resent the same filter. The error
  state now offers Clear filters.
- Clicking Create read-only key again failed with a batch error on the
  unique role name. An existing key or role with that name is reused.
On table load, list active API keys whose role grants API GET on this
table (this service or all services; *, _table/*, the table, or the
table's children). The first is shown with copy-curl and a link to its
role, with a picker when there are several. Create only appears when no
key has access, so a reload no longer drops the key you just made.
Roles with read access but no API key were hidden. The picker now lists
each such role (keyed roles first); a keyless role offers Create key,
which binds a new API key to that role.
The field already narrows as you type, but the 'Pick one' placeholder
made it look like a plain dropdown. Search icon plus 'Type to search
roles' placeholder.
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