feat(settings): admin-only Import & Export page for tenant data #423

Merged
nrupard merged 2 commits from feat/mapps-364-import-export into main 2026-07-09 23:16:49 +02:00
Owner

Implements MAPPS-364 (slice of PMS-646): an admin-only "Import & Export" page in mokosh-apps for tenant data backup/restore, entirely in the UI (no CLI/docker).

What changed

New page /settings/import-export (settings::ImportExportSettingsPage), admin-gated via use_is_admin + AdminOnlyNotice, filed under the Integrations settings group (route + wrapper + SETTINGS_SURFACES row).

Export panel - a Download button calls GET /api/v1/data/export with the bearer and saves the returned JSON attachment using the server's Content-Disposition filename. The bearer lives in WASM memory (not a cookie), so a plain <a href> can't carry it; this adds:

  • fetch::api::get_authed_bytes(path) - bearer GET returning raw bytes + the parsed Content-Disposition filename.
  • utils::download::save_bytes_as_file(bytes, filename) - wraps bytes in a Blob and clicks a synthesized anchor.
  • web-sys features Blob, Url, HtmlAnchorElement.

Import panel - a file picker reads the exported JSON via Dioxus evt.files() / FileData::read_string; a destructive-confirmation input requires typing the current tenant's name (auth.active_org_name(), which matches the backend confirm check against tenants.name) before the Import button enables. On submit it POSTs { confirm, export } to POST /api/v1/data/import via the existing JSON post_authed_typed (import is JSON, not multipart) and renders the per-table row-count summary. A clear red warning states import replaces all current tenant data. The write is also blocked while the server is unreachable (use_can_mutate).

Verified locally

Ran the CI gate against the rust-builder-glibc image: cargo fmt --all --check, cargo clippy --all-targets -- -D warnings, cargo check --target wasm32-unknown-unknown, and cargo test --lib (214 passed) all pass. Notably this caught that Dioxus 0.7.7's file API is evt.files() -> Vec<FileData> (not the older FileEngine), which is what the code now uses.

Acceptance criteria

  • Admin-only page exists (nav entry + route), blocked for non-admins.
  • Export downloads the snapshot via GET /data/export using the server filename; errors surfaced.
  • Import: file picker + typed tenant-name confirmation gate the button; success shows the per-table row-count summary; errors surface the server message; destructive nature warned.
  • No CLI/docker step needed.

Notes

  • Confirmation uses active_org_name() (the org name shown in the switcher, = tenants.name). If that ever diverged from tenants.name the server would 400 with "confirmation does not match"; the same string is displayed and client-gated, so they stay consistent.
  • Filed under Integrations (broadened its group description). A dedicated "Data" settings group is a trivial future move (one group: change) if preferred.
  • The export filename is mokosh-export-<tenant_uuid>.json (server-set); the client just honors it.
Implements MAPPS-364 (slice of PMS-646): an admin-only "Import & Export" page in mokosh-apps for tenant data backup/restore, entirely in the UI (no CLI/docker). ## What changed **New page** `/settings/import-export` (`settings::ImportExportSettingsPage`), admin-gated via `use_is_admin` + `AdminOnlyNotice`, filed under the Integrations settings group (route + wrapper + `SETTINGS_SURFACES` row). **Export panel** - a Download button calls `GET /api/v1/data/export` with the bearer and saves the returned JSON attachment using the server's `Content-Disposition` filename. The bearer lives in WASM memory (not a cookie), so a plain `<a href>` can't carry it; this adds: - `fetch::api::get_authed_bytes(path)` - bearer GET returning raw bytes + the parsed `Content-Disposition` filename. - `utils::download::save_bytes_as_file(bytes, filename)` - wraps bytes in a Blob and clicks a synthesized anchor. - web-sys features `Blob`, `Url`, `HtmlAnchorElement`. **Import panel** - a file picker reads the exported JSON via Dioxus `evt.files()` / `FileData::read_string`; a destructive-confirmation input requires typing the current tenant's name (`auth.active_org_name()`, which matches the backend `confirm` check against `tenants.name`) before the Import button enables. On submit it POSTs `{ confirm, export }` to `POST /api/v1/data/import` via the existing JSON `post_authed_typed` (import is JSON, not multipart) and renders the per-table row-count summary. A clear red warning states import replaces all current tenant data. The write is also blocked while the server is unreachable (`use_can_mutate`). ## Verified locally Ran the CI gate against the `rust-builder-glibc` image: `cargo fmt --all --check`, `cargo clippy --all-targets -- -D warnings`, `cargo check --target wasm32-unknown-unknown`, and `cargo test --lib` (214 passed) all pass. Notably this caught that Dioxus 0.7.7's file API is `evt.files() -> Vec<FileData>` (not the older `FileEngine`), which is what the code now uses. ## Acceptance criteria - [x] Admin-only page exists (nav entry + route), blocked for non-admins. - [x] Export downloads the snapshot via `GET /data/export` using the server filename; errors surfaced. - [x] Import: file picker + typed tenant-name confirmation gate the button; success shows the per-table row-count summary; errors surface the server message; destructive nature warned. - [x] No CLI/docker step needed. ## Notes - Confirmation uses `active_org_name()` (the org name shown in the switcher, = `tenants.name`). If that ever diverged from `tenants.name` the server would 400 with "confirmation does not match"; the same string is displayed and client-gated, so they stay consistent. - Filed under Integrations (broadened its group description). A dedicated "Data" settings group is a trivial future move (one `group:` change) if preferred. - The export filename is `mokosh-export-<tenant_uuid>.json` (server-set); the client just honors it.
feat(settings): admin-only Import & Export page for tenant data
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m28s
4fb7648378
Add an admin-only "Import & Export" settings page (MAPPS-364, server PMS-646) so a tenant admin can back up and restore data entirely from the UI, no CLI or docker step.

Export: a Download button fetches GET /api/v1/data/export with the bearer and saves the returned JSON attachment, honoring the server Content-Disposition filename. Because the bearer lives in WASM memory (not a cookie), a plain <a href> cannot carry it, so this adds a fetch-layer helper `get_authed_bytes` (bearer GET returning raw bytes + the parsed filename) and a `utils::download::save_bytes_as_file` helper that wraps the bytes in a Blob and clicks a synthesized anchor. Enables the web-sys Blob/Url/HtmlAnchorElement features.

Import: a file picker reads the exported JSON via Dioxus `evt.files()`/`FileData::read_string`, and a destructive-confirmation input requires the admin to type the current tenant's name (matching the backend `confirm` guard against tenants.name) before the Import button enables. On submit it POSTs { confirm, export } to /api/v1/data/import via the existing JSON `post_authed_typed` (import is JSON, not multipart) and renders the per-table row-count summary the server returns. A clear warning states that import replaces all current tenant data.

Both actions are admin-gated (use_is_admin + AdminOnlyNotice); the page is filed under the Integrations settings group. Verified locally against the rust-builder image: fmt, clippy -D warnings, wasm check, and cargo test --lib all pass.

#MAPPS-364

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(settings): harden data export/import per code review
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m37s
Create release / Create release from merged PR (pull_request) Has been skipped
39f7bae073
Three review follow-ups on the Import & Export page:

- get_authed_bytes now feeds the server-reachable flag like the other fetch helpers: note_transport_error on a transport failure and note_response_status on any response, so a failed export updates the down-banner instead of leaving it stale.
- The download anchor is attached to the DOM (hidden) before click() and removed after, since a detached-anchor click is not honored in every browser. Adds the web-sys Node feature.
- After a successful import the success panel explains that all records were replaced and re-keyed and offers a "Reload the app" button, since the wipe-and-replace invalidates the rest of the SPA's cached data.

Verified against the rust-builder image: fmt, clippy -D warnings, and wasm check all pass.

#MAPPS-364

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard scheduled this pull request to auto merge when all checks succeed 2026-07-09 23:16:20 +02:00
nrupard deleted branch feat/mapps-364-import-export 2026-07-09 23:16:50 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!423
No description provided.