portal-mojo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +55 -0
- package/docs/DetailView.md +173 -0
- package/docs/ModelTable.md +128 -0
- package/docs/README.md +98 -0
- package/docs/admin-assistant.md +72 -0
- package/docs/admin-bouncer.md +120 -0
- package/docs/admin-cloudwatch.md +38 -0
- package/docs/admin-credentials.md +116 -0
- package/docs/admin-dashboard.md +15 -0
- package/docs/admin-devices-geoip.md +284 -0
- package/docs/admin-dns-certificates.md +122 -0
- package/docs/admin-dns-records.md +65 -0
- package/docs/admin-dns-registrar.md +33 -0
- package/docs/admin-dns.md +80 -0
- package/docs/admin-identity-users.md +103 -0
- package/docs/admin-incidents.md +71 -0
- package/docs/admin-jobs.md +426 -0
- package/docs/admin-members.md +112 -0
- package/docs/admin-messaging.md +56 -0
- package/docs/admin-metrics-explorer.md +63 -0
- package/docs/admin-monitoring.md +99 -0
- package/docs/admin-network-security.md +393 -0
- package/docs/admin-phonehub.md +27 -0
- package/docs/admin-push.md +36 -0
- package/docs/admin-rules.md +87 -0
- package/docs/admin-security-tickets.md +40 -0
- package/docs/admin-settings.md +78 -0
- package/docs/admin-shortlinks.md +32 -0
- package/docs/admin-storage.md +141 -0
- package/docs/auth-pages.md +212 -0
- package/docs/auth.md +58 -0
- package/docs/calendar.md +122 -0
- package/docs/charts.md +303 -0
- package/docs/client.md +206 -0
- package/docs/collection-multiselect.md +117 -0
- package/docs/collection-select.md +132 -0
- package/docs/combobox.md +105 -0
- package/docs/dataview.md +226 -0
- package/docs/datepicker.md +151 -0
- package/docs/daterange.md +170 -0
- package/docs/datetimepicker.md +249 -0
- package/docs/defineModel.md +72 -0
- package/docs/detail-primitives.md +174 -0
- package/docs/feedback.md +85 -0
- package/docs/file-upload-ux.md +146 -0
- package/docs/forms.md +435 -0
- package/docs/grouping-and-fmt.md +98 -0
- package/docs/idioms.md +112 -0
- package/docs/image-editor.md +101 -0
- package/docs/loading.md +242 -0
- package/docs/location-address.md +76 -0
- package/docs/markdown.md +203 -0
- package/docs/menus-and-access.md +89 -0
- package/docs/multiselect-dropdown.md +153 -0
- package/docs/params.md +73 -0
- package/docs/popover.md +130 -0
- package/docs/realtime.md +61 -0
- package/docs/record-feed.md +238 -0
- package/docs/right-panel.md +42 -0
- package/docs/tabs-and-form-wizard.md +60 -0
- package/docs/taginput.md +120 -0
- package/docs/timepicker.md +173 -0
- package/docs/timezone-select.md +147 -0
- package/docs/worldmap.md +302 -0
- package/package.json +55 -0
- package/src/admin/assistant/AssistantFeed.tsx +29 -0
- package/src/admin/assistant/AssistantPanel.tsx +65 -0
- package/src/admin/assistant/api.ts +17 -0
- package/src/admin/assistant/data.ts +63 -0
- package/src/admin/assistant/index.ts +21 -0
- package/src/admin/assistant/launchers.tsx +33 -0
- package/src/admin/assistant/pages.tsx +78 -0
- package/src/admin/assistant/streaming.ts +238 -0
- package/src/admin/assistant/types.ts +30 -0
- package/src/admin/bouncer/devices.tsx +230 -0
- package/src/admin/bouncer/index.ts +34 -0
- package/src/admin/bouncer/models.ts +199 -0
- package/src/admin/bouncer/signals.tsx +182 -0
- package/src/admin/bouncer/signatures.tsx +220 -0
- package/src/admin/cloudwatch/CloudWatchChart.tsx +61 -0
- package/src/admin/cloudwatch/CloudWatchDashboardPage.tsx +52 -0
- package/src/admin/cloudwatch/CloudWatchResourceDetail.tsx +33 -0
- package/src/admin/cloudwatch/data.ts +163 -0
- package/src/admin/cloudwatch/index.ts +16 -0
- package/src/admin/credentials/group-api-keys.tsx +433 -0
- package/src/admin/credentials/index.ts +4 -0
- package/src/admin/credentials/models.ts +244 -0
- package/src/admin/credentials/secret-dialog.tsx +77 -0
- package/src/admin/credentials/webhook-subscriptions.tsx +423 -0
- package/src/admin/dashboard/AdminDashboardPage.tsx +101 -0
- package/src/admin/dashboard/data.ts +60 -0
- package/src/admin/dashboard/index.ts +2 -0
- package/src/admin/dns/CertificateDetail.tsx +115 -0
- package/src/admin/dns/CertificateLifecyclePoller.tsx +141 -0
- package/src/admin/dns/CertificateRequestDialog.tsx +103 -0
- package/src/admin/dns/CertificatesPage.tsx +85 -0
- package/src/admin/dns/DnsRecordEditor.tsx +110 -0
- package/src/admin/dns/DnsRecordsPage.tsx +36 -0
- package/src/admin/dns/DnsRecordsPanel.tsx +71 -0
- package/src/admin/dns/DomainCertificatesSection.tsx +40 -0
- package/src/admin/dns/DomainDetail.tsx +41 -0
- package/src/admin/dns/DomainOnboardingDialog.tsx +27 -0
- package/src/admin/dns/DomainPurchaseDetail.tsx +11 -0
- package/src/admin/dns/DomainPurchaseWizard.tsx +112 -0
- package/src/admin/dns/DomainPurchasesPage.tsx +16 -0
- package/src/admin/dns/DomainsPage.tsx +27 -0
- package/src/admin/dns/ProviderCredentialsPage.tsx +304 -0
- package/src/admin/dns/RegistrantContactPage.tsx +42 -0
- package/src/admin/dns/api.ts +515 -0
- package/src/admin/dns/certificate-data.ts +221 -0
- package/src/admin/dns/data.ts +32 -0
- package/src/admin/dns/dns-data.ts +266 -0
- package/src/admin/dns/dns-integration.ts +24 -0
- package/src/admin/dns/domain-links.ts +37 -0
- package/src/admin/dns/index.ts +86 -0
- package/src/admin/dns/models.ts +418 -0
- package/src/admin/dns/purchase-data.ts +67 -0
- package/src/admin/dns/registrant-data.ts +52 -0
- package/src/admin/identity/index.ts +5 -0
- package/src/admin/identity/members/GroupMembersPanel.tsx +146 -0
- package/src/admin/identity/members/MemberDetail.tsx +226 -0
- package/src/admin/identity/members/MembersPage.tsx +144 -0
- package/src/admin/identity/members/index.ts +24 -0
- package/src/admin/identity/members/member-flows.tsx +132 -0
- package/src/admin/identity/members/models.ts +204 -0
- package/src/admin/identity/users/UserDetail.tsx +257 -0
- package/src/admin/identity/users/UsersPage.tsx +163 -0
- package/src/admin/identity/users/index.ts +27 -0
- package/src/admin/identity/users/models.ts +232 -0
- package/src/admin/identity/users/sections/ApiKeysSection.tsx +119 -0
- package/src/admin/identity/users/sections/AuditSection.tsx +190 -0
- package/src/admin/identity/users/sections/DevicesSection.tsx +140 -0
- package/src/admin/identity/users/sections/GroupsSection.tsx +64 -0
- package/src/admin/identity/users/sections/LoginsSection.tsx +106 -0
- package/src/admin/identity/users/sections/MetadataPanel.tsx +36 -0
- package/src/admin/identity/users/sections/NotificationsSection.tsx +117 -0
- package/src/admin/identity/users/sections/OAuthSection.tsx +72 -0
- package/src/admin/identity/users/sections/OverviewSection.tsx +109 -0
- package/src/admin/identity/users/sections/PermissionsSection.tsx +35 -0
- package/src/admin/identity/users/sections/PersonalSection.tsx +73 -0
- package/src/admin/identity/users/sections/ProfileSection.tsx +175 -0
- package/src/admin/identity/users/sections/SecuritySection.tsx +123 -0
- package/src/admin/identity/users/sections/actions.tsx +752 -0
- package/src/admin/identity/users/sections/permission-catalog.ts +138 -0
- package/src/admin/identity/users/sections/queries.ts +74 -0
- package/src/admin/identity/users/sections/shared.tsx +299 -0
- package/src/admin/incidents/EventDetail.tsx +43 -0
- package/src/admin/incidents/EventsPage.tsx +52 -0
- package/src/admin/incidents/IncidentDetail.tsx +68 -0
- package/src/admin/incidents/IncidentsPage.tsx +102 -0
- package/src/admin/incidents/forensics.tsx +73 -0
- package/src/admin/incidents/index.ts +16 -0
- package/src/admin/incidents/models.ts +113 -0
- package/src/admin/incidents/sanitize.ts +71 -0
- package/src/admin/index.ts +383 -0
- package/src/admin/jobs/JobDashboardPage.tsx +111 -0
- package/src/admin/jobs/JobDetail.tsx +721 -0
- package/src/admin/jobs/JobRunnersPage.tsx +216 -0
- package/src/admin/jobs/JobsTablePage.tsx +122 -0
- package/src/admin/jobs/RunnerDetail.tsx +576 -0
- package/src/admin/jobs/ScheduledTaskDetail.tsx +421 -0
- package/src/admin/jobs/ScheduledTasksPage.tsx +167 -0
- package/src/admin/jobs/columns.tsx +297 -0
- package/src/admin/jobs/control.ts +488 -0
- package/src/admin/jobs/index.ts +85 -0
- package/src/admin/jobs/models.ts +599 -0
- package/src/admin/jobs/queries.ts +272 -0
- package/src/admin/jobs/scheduled-task-form.tsx +493 -0
- package/src/admin/jobs/sections/JobChannelsPanel.tsx +142 -0
- package/src/admin/jobs/sections/JobOperationsSection.tsx +423 -0
- package/src/admin/jobs/sections/JobRunnersStrip.tsx +82 -0
- package/src/admin/jobs/sections/JobThroughputSection.tsx +118 -0
- package/src/admin/messaging/EmailDomainWizard.tsx +24 -0
- package/src/admin/messaging/EmailDomainsPage.tsx +17 -0
- package/src/admin/messaging/EmailTemplatesPage.tsx +13 -0
- package/src/admin/messaging/MailboxesPage.tsx +15 -0
- package/src/admin/messaging/PublicMessagesPage.tsx +13 -0
- package/src/admin/messaging/SandboxedEmailPreview.tsx +23 -0
- package/src/admin/messaging/SentMessagesPage.tsx +11 -0
- package/src/admin/messaging/api.ts +17 -0
- package/src/admin/messaging/data.ts +10 -0
- package/src/admin/messaging/index.ts +23 -0
- package/src/admin/messaging/models.ts +76 -0
- package/src/admin/messaging/push/PushPage.tsx +44 -0
- package/src/admin/messaging/push/api.ts +17 -0
- package/src/admin/messaging/push/index.ts +9 -0
- package/src/admin/messaging/push/models.ts +43 -0
- package/src/admin/messaging/sanitize.ts +44 -0
- package/src/admin/monitoring/LogInspector.tsx +160 -0
- package/src/admin/monitoring/LogsPage.tsx +135 -0
- package/src/admin/monitoring/MetricsExplorerPage.tsx +360 -0
- package/src/admin/monitoring/MetricsPermissionsPage.tsx +166 -0
- package/src/admin/monitoring/MetricsSourcePicker.tsx +136 -0
- package/src/admin/monitoring/index.ts +55 -0
- package/src/admin/monitoring/metrics-explorer-client.ts +229 -0
- package/src/admin/monitoring/metrics-explorer-data.ts +158 -0
- package/src/admin/monitoring/models.ts +155 -0
- package/src/admin/network/BlockedIPsPage.tsx +194 -0
- package/src/admin/network/BlockedIpDetail.tsx +262 -0
- package/src/admin/network/FirewallLogPage.tsx +115 -0
- package/src/admin/network/IPSetDetail.tsx +280 -0
- package/src/admin/network/IPSetEditor.tsx +191 -0
- package/src/admin/network/IPSetsPage.tsx +177 -0
- package/src/admin/network/geofence/BlocksTab.tsx +289 -0
- package/src/admin/network/geofence/ExemptionsTab.tsx +349 -0
- package/src/admin/network/geofence/GeofencingPage.tsx +119 -0
- package/src/admin/network/geofence/PostureHeader.tsx +184 -0
- package/src/admin/network/geofence/RuleEditor.tsx +176 -0
- package/src/admin/network/geofence/RulesTab.tsx +254 -0
- package/src/admin/network/geofence/SimulatorTab.tsx +281 -0
- package/src/admin/network/geofence/geofence-data.ts +674 -0
- package/src/admin/network/index.ts +68 -0
- package/src/admin/network/models.ts +607 -0
- package/src/admin/phonehub/PhoneHubPage.tsx +57 -0
- package/src/admin/phonehub/api.ts +47 -0
- package/src/admin/phonehub/data.ts +6 -0
- package/src/admin/phonehub/index.ts +10 -0
- package/src/admin/phonehub/models.ts +68 -0
- package/src/admin/rules/HandlerChainBuilder.tsx +51 -0
- package/src/admin/rules/RuleSetDetailPage.tsx +33 -0
- package/src/admin/rules/RuleSetsPage.tsx +33 -0
- package/src/admin/rules/editors.tsx +54 -0
- package/src/admin/rules/handler-dsl.ts +213 -0
- package/src/admin/rules/index.ts +13 -0
- package/src/admin/rules/models.ts +112 -0
- package/src/admin/security/devices/LoginEventDetail.tsx +123 -0
- package/src/admin/security/devices/LoginLocationMap.tsx +321 -0
- package/src/admin/security/devices/LoginLocationsPage.tsx +162 -0
- package/src/admin/security/devices/UserDeviceDetail.tsx +546 -0
- package/src/admin/security/devices/UserDeviceLocationDetail.tsx +272 -0
- package/src/admin/security/devices/UserDevicesPage.tsx +84 -0
- package/src/admin/security/devices/index.ts +40 -0
- package/src/admin/security/devices/models.ts +470 -0
- package/src/admin/security/geoip/GeoIpCachePage.tsx +135 -0
- package/src/admin/security/geoip/GeoIpDossier.tsx +804 -0
- package/src/admin/security/geoip/geoip-forms.ts +102 -0
- package/src/admin/security/geoip/index.ts +27 -0
- package/src/admin/security/geoip/models.ts +397 -0
- package/src/admin/security/index.ts +31 -0
- package/src/admin/security/models.ts +137 -0
- package/src/admin/security/tickets.tsx +562 -0
- package/src/admin/security-permissions.ts +5 -0
- package/src/admin/settings/SettingDetail.tsx +57 -0
- package/src/admin/settings/SettingEditor.tsx +157 -0
- package/src/admin/settings/SettingsPage.tsx +72 -0
- package/src/admin/settings/index.ts +19 -0
- package/src/admin/settings/model.ts +143 -0
- package/src/admin/shortlinks/ShortlinkDetail.tsx +55 -0
- package/src/admin/shortlinks/ShortlinkHistoryPage.tsx +18 -0
- package/src/admin/shortlinks/ShortlinksPage.tsx +59 -0
- package/src/admin/shortlinks/index.ts +18 -0
- package/src/admin/shortlinks/models.ts +246 -0
- package/src/admin/storage/BackendsPage.tsx +43 -0
- package/src/admin/storage/BucketDetail.tsx +77 -0
- package/src/admin/storage/BucketsPage.tsx +64 -0
- package/src/admin/storage/FileManagerDetail.tsx +36 -0
- package/src/admin/storage/FilePreview.tsx +54 -0
- package/src/admin/storage/FileUploadSurface.tsx +250 -0
- package/src/admin/storage/FileView.tsx +109 -0
- package/src/admin/storage/FilesPage.tsx +53 -0
- package/src/admin/storage/file-renditions.ts +99 -0
- package/src/admin/storage/index.ts +31 -0
- package/src/admin/storage/models.ts +585 -0
- package/src/admin/storage/storage-dialogs.tsx +80 -0
- package/src/charts/CircularProgress.tsx +281 -0
- package/src/charts/KPIStrip.tsx +162 -0
- package/src/charts/KPITile.tsx +125 -0
- package/src/charts/MetricsChart.tsx +459 -0
- package/src/charts/MetricsMiniWidget.tsx +588 -0
- package/src/charts/MiniChart.tsx +283 -0
- package/src/charts/PieChart.tsx +354 -0
- package/src/charts/SeriesChart.tsx +378 -0
- package/src/charts/chart-dialogs.tsx +149 -0
- package/src/charts/exportChart.ts +144 -0
- package/src/charts/index.ts +28 -0
- package/src/charts/pie-math.ts +127 -0
- package/src/charts/stats.ts +265 -0
- package/src/charts/worldmap/WorldMap.tsx +649 -0
- package/src/charts/worldmap/countryCentroids.ts +304 -0
- package/src/charts/worldmap/geo.ts +293 -0
- package/src/charts/worldmap/use-world-land.ts +47 -0
- package/src/charts/worldmap/world-land.ts +18 -0
- package/src/charts/worldmap/worldmap-data.ts +285 -0
- package/src/client/auth.ts +737 -0
- package/src/client/client.ts +268 -0
- package/src/client/duid.ts +31 -0
- package/src/client/errors.ts +33 -0
- package/src/client/group-context.ts +28 -0
- package/src/client/group.tsx +126 -0
- package/src/client/hooks.ts +50 -0
- package/src/client/index.ts +37 -0
- package/src/client/jwt.ts +75 -0
- package/src/client/location.ts +285 -0
- package/src/client/lookups.ts +41 -0
- package/src/client/markdown-fallback.tsx +115 -0
- package/src/client/markdown-parse.ts +386 -0
- package/src/client/markdown-sanitize.ts +217 -0
- package/src/client/markdown.ts +122 -0
- package/src/client/me.ts +188 -0
- package/src/client/mock.ts +9984 -0
- package/src/client/model.ts +227 -0
- package/src/client/params.ts +292 -0
- package/src/client/realtime-mock.ts +221 -0
- package/src/client/realtime.ts +561 -0
- package/src/client/record-feed.ts +329 -0
- package/src/client/safe-export.ts +71 -0
- package/src/client/types.ts +278 -0
- package/src/client/upload.ts +421 -0
- package/src/ui/AddressField.tsx +273 -0
- package/src/ui/AttachmentQueue.tsx +92 -0
- package/src/ui/CollectionMultiSelect.tsx +622 -0
- package/src/ui/CollectionSelect.tsx +517 -0
- package/src/ui/ComboBox.tsx +336 -0
- package/src/ui/DetailView.tsx +438 -0
- package/src/ui/FileDrop.tsx +194 -0
- package/src/ui/FileField.tsx +369 -0
- package/src/ui/FilterBar.tsx +269 -0
- package/src/ui/FormFields.tsx +77 -0
- package/src/ui/FormView.tsx +360 -0
- package/src/ui/FormWizard.tsx +336 -0
- package/src/ui/GroupSwitcher.tsx +202 -0
- package/src/ui/Guarded.tsx +16 -0
- package/src/ui/MarkdownView.tsx +89 -0
- package/src/ui/ModelTable.tsx +848 -0
- package/src/ui/MultiSelectDropdown.tsx +390 -0
- package/src/ui/Popover.tsx +204 -0
- package/src/ui/RecordFeed.tsx +627 -0
- package/src/ui/RequiresGroup.tsx +32 -0
- package/src/ui/RightPanel.tsx +107 -0
- package/src/ui/SidebarNav.tsx +330 -0
- package/src/ui/Tabs.tsx +161 -0
- package/src/ui/TagInput.tsx +381 -0
- package/src/ui/ThemeProvider.tsx +52 -0
- package/src/ui/UploadQueue.tsx +603 -0
- package/src/ui/armed-button.tsx +91 -0
- package/src/ui/date/Calendar.tsx +498 -0
- package/src/ui/date/DatePicker.tsx +289 -0
- package/src/ui/date/DateRangePicker.tsx +280 -0
- package/src/ui/date/DateTimePicker.tsx +587 -0
- package/src/ui/date/PresetRail.tsx +165 -0
- package/src/ui/date/TimePicker.tsx +624 -0
- package/src/ui/date/TimezoneSelect.tsx +267 -0
- package/src/ui/date/fns.ts +679 -0
- package/src/ui/detail/DataView.tsx +692 -0
- package/src/ui/detail/FlowStrip.tsx +80 -0
- package/src/ui/detail/KnownFieldsCard.tsx +121 -0
- package/src/ui/detail/MetadataSection.tsx +219 -0
- package/src/ui/detail/StackTraceView.tsx +150 -0
- package/src/ui/detail/StatusPanel.tsx +73 -0
- package/src/ui/detail/Timeline.tsx +52 -0
- package/src/ui/detail/index.ts +12 -0
- package/src/ui/field-registry.tsx +428 -0
- package/src/ui/field-wire.ts +322 -0
- package/src/ui/form-autosave.ts +583 -0
- package/src/ui/format.ts +444 -0
- package/src/ui/grouping.ts +150 -0
- package/src/ui/image-editor/ImageEditor.tsx +484 -0
- package/src/ui/image-editor/canvas.ts +254 -0
- package/src/ui/image-editor/index.ts +3 -0
- package/src/ui/image-editor/math.ts +416 -0
- package/src/ui/index.ts +58 -0
- package/src/ui/loading.tsx +604 -0
- package/src/ui/menu-registry.ts +213 -0
- package/src/ui/modal.tsx +297 -0
- package/src/ui/password.tsx +302 -0
- package/src/ui/schema-form-core.tsx +315 -0
- package/src/ui/toast.tsx +219 -0
- package/src/ui/ui.tsx +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# portal-mojo (the package)
|
|
2
|
+
|
|
3
|
+
The React portal toolkit for django-mojo. Ships as **TypeScript source** via
|
|
4
|
+
subpath exports — consumers compile it with their own Vite + TS
|
|
5
|
+
(`moduleResolution: "bundler"`); there is no package build step here.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install portal-mojo react react-dom react-router-dom @tanstack/react-query
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Per-component reference docs (written for AI context): [`docs/`](docs/README.md).**
|
|
12
|
+
Live demos for everything: run `npm run dev:showcase` → **Develop → Components**.
|
|
13
|
+
|
|
14
|
+
## Subpath surfaces
|
|
15
|
+
|
|
16
|
+
| Subpath | Surface |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `portal-mojo/client` | Typed django-mojo protocol layer: envelope unwrap at exactly one boundary (a failed save **rejects**), `start`/`size` paging, `'-field'` sort, Django lookups, the URL-synced `useTableParams` store (single source of truth for table state), TanStack Query hooks. Auth client: password / magic-link / passkey login, forgot/reset, cross-origin handoff (`?auth_code=` scrub-before-network), single-flight refresh + pre-request gate (synthetic-401 reject, `/api/token/refresh` recursion guard), `X-Mojo-UID` device header, `getAuthSnapshot`/`subscribeAuth` for React. Auth-challenged realtime transport: `RealtimeProvider`/`RealtimeClient`, refcounted topics, typed event projection, and a deterministic mock. Boot auth with `initAuth()`. The in-memory HTTP mock transport lives here too — it is the wire contract's executable spec and evolves in lockstep with the client (any seeded active user logs in with email + `"mojo"`). |
|
|
19
|
+
| `portal-mojo/ui` | Mission-control UI: `ModelTable`, `FilterBar`/`FilterPills`, `SchemaForm`/`formModal`, `DetailView`, `RecordFeed`, `AttachmentQueue`/`UploadQueue`, `ImageEditor`/`imageEditorModal`, awaitable native-`<dialog>` `modal`, `toast`, `ThemeProvider`, `Guarded` (permission slot), `GroupSwitcher` (searchable tree selector), `RequiresGroup`, the sidebar engine (`registerMenus`/`setDefaultMenu` registry + `SidebarNav` — static or searchable accordion with a compact icon rail; global and group scopes are explicit), `Badge`/`MetricCard`/`Spark`, `fmt` formatter namespace. |
|
|
20
|
+
| `portal-mojo/charts` | Dependency-free SVG charts: `SeriesChart` (line/bar/area, stacked bars, legend toggle, crosshair tooltip) and `MetricsChart` (granularity/range/type control bar for `/api/metrics/fetch`). |
|
|
21
|
+
| `portal-mojo/admin` | Global Admin **section bundles**, dual-mount: the standalone back-office portal mounts all of them at root; a product portal embeds them under a global "System" area. `AdminSection` contract + `adminSectionsMenu()` (sections → domain-grouped sidebar menu, mount-point-relative, with section + route permission clauses). Admin scope masks active-group membership even when embedded. Reusable domains include credentials, monitoring, security operations, settings, Bouncer, Members, and the Assistant control plane with narrow realtime streaming plus authoritative REST fallback/reconciliation. |
|
|
22
|
+
|
|
23
|
+
## What the consuming app must provide
|
|
24
|
+
|
|
25
|
+
- **Theme tokens + component CSS.** Components render semantic classes
|
|
26
|
+
(`panel`, `chip`, `tbl`, …) styled by the app's `theme.css` under
|
|
27
|
+
`data-theme="light"` **and** `"dark"` (reference set:
|
|
28
|
+
`apps/portal/src/theme.css`).
|
|
29
|
+
- **Tailwind scan of this package.** In an installed app's CSS:
|
|
30
|
+
`@source "../node_modules/portal-mojo/src";` (adjust the relative path from
|
|
31
|
+
that CSS file). Utility classes used in package components are generated by
|
|
32
|
+
the app's Tailwind pass. Monorepo development may scan
|
|
33
|
+
`../../../packages/portal-mojo/src` instead.
|
|
34
|
+
- **bootstrap-icons.** Components emit `bi bi-*` classes; the app imports the
|
|
35
|
+
icon font once.
|
|
36
|
+
- **Providers.** Every shell supplies `QueryClientProvider` (spread
|
|
37
|
+
`mojoQueryDefaults()` into its defaults), a hash router (`useTableParams` is
|
|
38
|
+
built on react-router's `useSearchParams`), `ThemeProvider`, `ModalHost`, and
|
|
39
|
+
`ToastHost`. Mount one auth-owned `RealtimeProvider` before any realtime hook
|
|
40
|
+
or Assistant surface; individual permission-gated consumers own topic/event
|
|
41
|
+
lifetimes, not the shared connection. Product portals additionally install
|
|
42
|
+
`GroupProvider` so their group-scoped pages and menus can fold active-member
|
|
43
|
+
permissions into `useCan`/`Guarded`. The standalone Admin shell deliberately
|
|
44
|
+
does not install group context; it does install one
|
|
45
|
+
`RightPanelProvider`/`RightPanelSlot` for the globally gated Assistant panel.
|
|
46
|
+
- **`VITE_MOJO_API`.** Unset → the in-package mock transport (dev default);
|
|
47
|
+
set to a django-mojo origin → the real backend, same code path.
|
|
48
|
+
|
|
49
|
+
## Rules
|
|
50
|
+
|
|
51
|
+
The non-negotiables live in the repo root `PLAN.md` ("Architecture rules") and
|
|
52
|
+
`CLAUDE.md`. Short list: params store owns table state; one envelope-unwrap
|
|
53
|
+
boundary; failure is unmissable; controlled inputs only; models are
|
|
54
|
+
definitions + hooks (TanStack Query owns cache); `ReactNode` slots, never
|
|
55
|
+
trusted-HTML strings; both themes from day one.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# DetailView — the UserView house style
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import {
|
|
5
|
+
DetailView, Eyebrow, FlatRow, SecurityItem,
|
|
6
|
+
type Section, type RailEntry, type DetailMenuEntry, type DetailViewProps,
|
|
7
|
+
} from 'portal-mojo/ui';
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The record-detail surface: identity header (avatar, title, subtitle,
|
|
11
|
+
chips, optional active switch, kebab menu, close) over a left rail of
|
|
12
|
+
sections with side-label dividers. Open it inside `modal.detail(...)` for
|
|
13
|
+
the standard row-click experience. Ported from web-mojo `SideNavView.js`
|
|
14
|
+
(sections, gating, badges) + `ContextMenu.js` (the kebab).
|
|
15
|
+
|
|
16
|
+
**Admin convention:** every record inspection launched from an Admin table
|
|
17
|
+
uses this KISS detail-modal pattern unless the product explicitly specifies a
|
|
18
|
+
different presentation. Top-level Admin resources remain pages in the global
|
|
19
|
+
Admin sidenav; the selected record does not become a child route, drawer, or
|
|
20
|
+
persistent right panel by default. Focused edit/action dialogs may stack above
|
|
21
|
+
the detail through `modal.open` or `modal.confirm`.
|
|
22
|
+
|
|
23
|
+
Because sections and menu entries can carry permissions, DetailView
|
|
24
|
+
resolves the live session (`useMe` + active-group member) — like the rest
|
|
25
|
+
of the toolkit it must render inside the app's `QueryClientProvider`
|
|
26
|
+
(mount `GroupProvider` too if member permissions should fold in).
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<DetailView
|
|
30
|
+
avatarName={u.display_name || u.username}
|
|
31
|
+
title={...} subtitle={u.email}
|
|
32
|
+
chips={[{ icon: 'bi-patch-check-fill', text: 'Email', tone: 'success' }]}
|
|
33
|
+
active={{ value: u.is_active, onChange: (next) => ... }}
|
|
34
|
+
onClose={onClose}
|
|
35
|
+
badges={{ sessions: openSessions }}
|
|
36
|
+
menuContext={u}
|
|
37
|
+
contextMenu={[
|
|
38
|
+
{ label: 'Resend invite', icon: 'bi-envelope-paper', when: (u) => u?.last_login == null, onSelect: ... },
|
|
39
|
+
{ divider: true },
|
|
40
|
+
{ label: 'Delete', icon: 'bi-trash', danger: true, permissions: ['manage_users', 'users'], onSelect: ... },
|
|
41
|
+
]}
|
|
42
|
+
sections={[
|
|
43
|
+
{ key: 'profile', label: 'Profile', icon: 'bi-person', render: () => <>…</> },
|
|
44
|
+
{ divider: 'Security' },
|
|
45
|
+
{ key: 'security', label: 'Security', icon: 'bi-shield-check', permissions: 'view_security', render: () => <>…</> },
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `active` is a CONTROLLED switch — cancelling a confirmation simply not
|
|
51
|
+
changing state snaps it back (the disable-flow pattern: off → collect
|
|
52
|
+
reason via `formModal`, cancelled → no state change).
|
|
53
|
+
- `sections` mixes `{key, label, icon, render, permissions?}` entries with
|
|
54
|
+
`{divider: 'Label'}` side-labels.
|
|
55
|
+
- `initialSection` picks the starting rail entry.
|
|
56
|
+
|
|
57
|
+
## Permission-gated sections (fail-closed)
|
|
58
|
+
|
|
59
|
+
`Section.permissions` is a permission name or an ANY-of array, resolved
|
|
60
|
+
exactly like `useCan` (signed-in user + active-group member; `admin` and
|
|
61
|
+
superuser pass everything; `sys.` pins to system level). **Fail-closed**:
|
|
62
|
+
while `me` loads, while anonymous, and on a failed check the section is
|
|
63
|
+
absent from the rail AND cannot be activated — there is no route to a
|
|
64
|
+
gated section's content, and its content is never mounted (not even
|
|
65
|
+
hidden).
|
|
66
|
+
|
|
67
|
+
- **Orphan dividers drop.** After gating, a `{divider}` label survives
|
|
68
|
+
only if the next visible entry is a section — trailing and doubled
|
|
69
|
+
dividers disappear with their group (a leading divider with visible
|
|
70
|
+
content below it stays). Source rule: `SideNavView._visibleSectionConfigs`.
|
|
71
|
+
- **Active self-heal.** If the ACTIVE section becomes invisible
|
|
72
|
+
(permission or schema change), selection falls back to the first visible
|
|
73
|
+
section — and the heal is STICKY (`_reconcileActiveSection` parity): the
|
|
74
|
+
old section regaining visibility does not steal selection back.
|
|
75
|
+
|
|
76
|
+
## Rail badges (controlled)
|
|
77
|
+
|
|
78
|
+
`badges?: Record<sectionKey, ReactNode>` — numbers, text, dots
|
|
79
|
+
(`<span className="rail-dot" />`), or any node, rendered at the right edge
|
|
80
|
+
of the rail entry. Primitive values (string/number — including `0`) get
|
|
81
|
+
the source's default muted pill; elements render as given, so
|
|
82
|
+
`<Badge tone>` owns its look. Nullish / `false` / `true` / `''` render
|
|
83
|
+
nothing.
|
|
84
|
+
|
|
85
|
+
**Deviation from web-mojo, by design:** the source's imperative
|
|
86
|
+
`setBadge(key, value)` patched the DOM in place. Here badges are a
|
|
87
|
+
CONTROLLED prop — React owns the state; update the prop and the rail
|
|
88
|
+
follows. There is no imperative handle.
|
|
89
|
+
|
|
90
|
+
## Header kebab menu
|
|
91
|
+
|
|
92
|
+
`contextMenu?: Array<{ label, icon?, permissions?, when?, onSelect, danger? } | { divider: true }>`
|
|
93
|
+
renders a three-dot button in the header gutter (before the close X) that
|
|
94
|
+
opens a `Popover` menu (`placement: bottom-end`, top-layer — works inside
|
|
95
|
+
`modal.detail`). Filtering is `ContextMenu.visibleItems` parity:
|
|
96
|
+
|
|
97
|
+
- `permissions` — same any-of, fail-closed gate as sections.
|
|
98
|
+
- `when(menuContext)` — visibility predicate over the `menuContext` prop
|
|
99
|
+
(by convention the record on display; `undefined` when not provided).
|
|
100
|
+
Re-evaluated every render, so entries track model state live.
|
|
101
|
+
- Hidden entries drop; separators that end up leading, trailing, or
|
|
102
|
+
doubled collapse — the menu never shows an orphaned rule.
|
|
103
|
+
- No surviving entries ⇒ no kebab button at all.
|
|
104
|
+
- Select runs `onSelect()` then closes the menu (source order). Outside
|
|
105
|
+
mousedown and Escape close it too (Popover semantics).
|
|
106
|
+
- `danger: true` styles the entry with `--bad` (destructive actions).
|
|
107
|
+
|
|
108
|
+
`DetailView` is generic over the context: `DetailView<TCtx>` — `TCtx` is
|
|
109
|
+
inferred from `menuContext`, and `when` receives `TCtx | undefined`.
|
|
110
|
+
|
|
111
|
+
## Lazy mount + keep-alive
|
|
112
|
+
|
|
113
|
+
A section's content mounts on FIRST activation and stays
|
|
114
|
+
mounted-but-hidden afterwards (CSS `hidden`, not unmount), so section
|
|
115
|
+
state — form drafts, scroll-free local state, in-flight queries —
|
|
116
|
+
survives switching away and back. This is the source's
|
|
117
|
+
unmount-not-destroy semantic translated to React.
|
|
118
|
+
|
|
119
|
+
- Never-activated sections don't mount at all (lazy).
|
|
120
|
+
- Permission revocation UNMOUNTS the revoked section entirely
|
|
121
|
+
(fail-closed beats keep-alive — revoked content must not linger in the
|
|
122
|
+
DOM); regaining the permission remounts it fresh, state gone.
|
|
123
|
+
- A section removed from the `sections` array unmounts and loses state;
|
|
124
|
+
re-adding it starts fresh (source `removeSection` destroyed the view).
|
|
125
|
+
- Components used inside `render` must be module-level (or otherwise
|
|
126
|
+
identity-stable) — an inline component literal re-created each parent
|
|
127
|
+
render remounts every time, defeating keep-alive.
|
|
128
|
+
|
|
129
|
+
## Misconfiguration warns — never silence
|
|
130
|
+
|
|
131
|
+
Invalid config falls back WITH a `console.warn` (once per problem per
|
|
132
|
+
instance, not per render):
|
|
133
|
+
|
|
134
|
+
| Problem | Fallback |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `permissions` not a string/string[] (or non-string array entries) | treated as DENIED (fail-closed; superuser/`admin` still pass) |
|
|
137
|
+
| `badges` key naming no section | ignored |
|
|
138
|
+
| duplicate section `key` | warned (activation + keep-alive key on it) |
|
|
139
|
+
| `initialSection` naming no section at all | falls back to first visible |
|
|
140
|
+
| contextMenu entry without `label`/`onSelect` | dropped |
|
|
141
|
+
| contextMenu `when` not a function | predicate ignored |
|
|
142
|
+
|
|
143
|
+
A merely GATED `initialSection` heals silently — that is normal
|
|
144
|
+
permission behavior, not misconfig.
|
|
145
|
+
|
|
146
|
+
## Row/section primitives
|
|
147
|
+
|
|
148
|
+
| Component | Use |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `Eyebrow` | small uppercase section heading inside a panel |
|
|
151
|
+
| `FlatRow` | label + value row; `action` adds the edit pencil (`actionIcon` to override, e.g. `bi-plus-lg` for empty values) |
|
|
152
|
+
| `SecurityItem` | icon + title + description row with a trailing slot (badge/button) |
|
|
153
|
+
|
|
154
|
+
Conventions from the Users proof: pencil actions open `formModal` with
|
|
155
|
+
current values as `initial`; a danger action lives in a `.danger-zone`
|
|
156
|
+
block at the bottom of a section; fields the wire doesn't carry for other
|
|
157
|
+
users (me-graph only: `has_passkey`, `requires_mfa`) don't get rows.
|
|
158
|
+
|
|
159
|
+
## Pitfalls
|
|
160
|
+
|
|
161
|
+
- **Gated `initialSection` lands on the first visible section** and stays
|
|
162
|
+
there even after grants finish loading (fail-closed + sticky heal, per
|
|
163
|
+
source). Don't point `initialSection` at a gated section expecting it
|
|
164
|
+
to activate late.
|
|
165
|
+
- **Hidden sections keep rendering** (they're mounted): their `render()`
|
|
166
|
+
closures re-run on every parent render and their queries stay live.
|
|
167
|
+
Keep section bodies pure-render; put expensive work behind hooks that
|
|
168
|
+
cache.
|
|
169
|
+
- **Badges are state, not commands** — to "setBadge", change the value
|
|
170
|
+
your `badges` prop is built from.
|
|
171
|
+
- The styles for the C1 chrome live in `apps/portal/src/theme/detailview.css`
|
|
172
|
+
(`.rail-badge`, `.rail-dot`, `.dv-menu*`, `.dv-keep`) — tokens only,
|
|
173
|
+
both themes.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# ModelTable — the server-driven table
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { ModelTable, ExpandingSearch, type Column, type Preset, type BatchAction, type RowId } from 'portal-mojo/ui';
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
## Row identity
|
|
8
|
+
|
|
9
|
+
The row constraint is `T extends { id: RowId }` where `RowId = number | string`.
|
|
10
|
+
Most django-mojo models use an integer pk, but some declare a 32-char uuid-hex
|
|
11
|
+
`CharField` primary key (`jobs.Job`, `jobs.ScheduledTask`) — so selection,
|
|
12
|
+
expansion, React keys and `BatchAction` targets are all keyed on the union. An
|
|
13
|
+
integer-id table is unaffected; nothing narrows an id back to `number`.
|
|
14
|
+
|
|
15
|
+
Every sort, filter, search and page is a **wire param the server answers**
|
|
16
|
+
— there is no client-side row work (rule #1). Table state lives in the
|
|
17
|
+
URL-synced params store. All features below are opt-in props; a bare
|
|
18
|
+
`<ModelTable model={M} title columns />` renders the base table.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<ModelTable<User>
|
|
22
|
+
model={UserModel} // or endpoint="/api/user"
|
|
23
|
+
title="Users" eyebrow="Account"
|
|
24
|
+
columns={COLUMNS} filters={FILTERS} presets={PRESETS}
|
|
25
|
+
defaultSort="-last_activity"
|
|
26
|
+
defaultParams={{ status__in: 'new,open' }}
|
|
27
|
+
selectable batchActions={BATCH}
|
|
28
|
+
columnChooser persistState persistKey="users"
|
|
29
|
+
exportFormats={['csv', 'json']}
|
|
30
|
+
exporter={safeExporter}
|
|
31
|
+
rowTone={(row) => row.priority >= 8 ? 'danger' : null}
|
|
32
|
+
autoRefresh={30}
|
|
33
|
+
rowExpand={(u) => <UserExpand u={u} />}
|
|
34
|
+
{...groupByRecency<User>('last_activity')}
|
|
35
|
+
onRowClick={(u) => openDetail(u.id)}
|
|
36
|
+
addLabel="Add User" onAdd={addUser}
|
|
37
|
+
/>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Columns
|
|
41
|
+
|
|
42
|
+
`Column<T> = { key, label, sortable?, align?: 'start'|'center'|'end',
|
|
43
|
+
hideable?, render?: (row) => ReactNode }`. `key` is the server field and
|
|
44
|
+
drives the sort param. `render` is the ONE cell prop (no formatter/template
|
|
45
|
+
aliases). `hideable: false` locks a column in the chooser (identity
|
|
46
|
+
columns).
|
|
47
|
+
|
|
48
|
+
## Filters (see also forms.md dialog types)
|
|
49
|
+
|
|
50
|
+
`FilterDef` types: `text` (`__icontains`), `select`, `multiselect`
|
|
51
|
+
(collapses to `field__in`), `boolean` (`trueLabel`/`falseLabel`), `number`
|
|
52
|
+
(`__gte`), `daterange` (the `dr_*` triple). Applied filters render as
|
|
53
|
+
editable pills; presets are param bundles with DERIVED active state.
|
|
54
|
+
|
|
55
|
+
## Selection + batch
|
|
56
|
+
|
|
57
|
+
`selectable` renders checkboxes independently of `batchActions` (decoupled
|
|
58
|
+
by design). Select-all covers the current page, with an indeterminate
|
|
59
|
+
state. Selection clears when the wire params change (page/sort/filter).
|
|
60
|
+
|
|
61
|
+
`BatchAction<T>` accepts either `run(row, prepared)` or the mutually exclusive
|
|
62
|
+
`runBatch(rows, prepared)`. Runner semantics: confirm → optional once-per-batch
|
|
63
|
+
`prepare(rows)` (resolve null to cancel; its value is
|
|
64
|
+
passed to every run — e.g. collect ONE disable reason) →
|
|
65
|
+
`Promise.allSettled(rows.map(run))` → toasts `"Label: N item(s) updated"`
|
|
66
|
+
/ `"N succeeded, M failed"` (warning) / `"failed for all N"` → always
|
|
67
|
+
clear selection + invalidate.
|
|
68
|
+
|
|
69
|
+
`runBatch` executes exactly once for selection-wide operations such as incident
|
|
70
|
+
merge. A rejection produces one actionable error; cancellation mutates
|
|
71
|
+
nothing. Selection clears and the endpoint invalidates after a non-cancelled
|
|
72
|
+
attempt. `rowTone(row)` emits semantic danger/warning queue classes.
|
|
73
|
+
|
|
74
|
+
## Column chooser + persistState
|
|
75
|
+
|
|
76
|
+
`columnChooser` adds the Columns dropdown; hidden set is view state, never
|
|
77
|
+
a mutation of your `columns` array. `persistState` remembers sort / size /
|
|
78
|
+
search / filters / hidden columns per table (`persistKey`, else
|
|
79
|
+
route+endpoint) — precedence URL > saved > defaults, saved `size` wins; the
|
|
80
|
+
first fetch waits for the restore so it queries the restored view.
|
|
81
|
+
"Reset to defaults" in the chooser clears the whole saved view.
|
|
82
|
+
|
|
83
|
+
`defaultParams` supplies route defaults for any query key, including Django
|
|
84
|
+
lookups such as `status__in`. They are part of the same params store as URL and
|
|
85
|
+
persisted values. Precedence is URL > persisted > defaults; `defaultSort`
|
|
86
|
+
remains the backwards-compatible sort shorthand and wins over
|
|
87
|
+
`defaultParams.sort` when both are present.
|
|
88
|
+
|
|
89
|
+
## autoRefresh
|
|
90
|
+
|
|
91
|
+
Seconds (5s floor). A plain interval whose tick SKIPS while: tab hidden,
|
|
92
|
+
window blurred, any `<dialog>` open, or a selection is active. No focus
|
|
93
|
+
listeners — `refetchOnWindowFocus` is globally off; the next interval tick
|
|
94
|
+
catches up. The refresh button spins on any fetch and its tooltip shows
|
|
95
|
+
the cadence.
|
|
96
|
+
|
|
97
|
+
## Row expand / groupBy
|
|
98
|
+
|
|
99
|
+
`rowExpand(row) => ReactNode` adds the chevron column and a full-width
|
|
100
|
+
detail row; single-open unless `rowExpandMultiple`; page changes collapse
|
|
101
|
+
rows that left the page. `groupBy(row) => string | null` interleaves
|
|
102
|
+
full-width header rows on key change — falsy = ungrouped tail (prior
|
|
103
|
+
section continues); labels via `groupHeaderLabel`; styles
|
|
104
|
+
`banner|mark|band|rule` (unknown → warn + banner). Labels are sticky so
|
|
105
|
+
they stay visible on horizontally-scrolled tables. Use the
|
|
106
|
+
`grouping-and-fmt.md` helpers; align the group field with `defaultSort` or
|
|
107
|
+
headers repeat (that repetition is the defined behavior, not a bug).
|
|
108
|
+
|
|
109
|
+
## Export
|
|
110
|
+
|
|
111
|
+
`exportFormats={['csv','json']}` → server-side export of the WHOLE
|
|
112
|
+
filtered set via `download_format` + `filename` (paging stripped).
|
|
113
|
+
`exporter(format, normalizedParams)` overrides that path. It receives the same
|
|
114
|
+
model-normalized params as the visible query, allowing sensitive domains to use
|
|
115
|
+
bounded, immediately sanitized, allowlisted client exports.
|
|
116
|
+
|
|
117
|
+
## Loading states
|
|
118
|
+
|
|
119
|
+
Uncached loads (cold, page/sort/filter changes — `isPlaceholderData`)
|
|
120
|
+
render the column-matched skeleton silhouette; cached pages render
|
|
121
|
+
instantly; background refetches never blank the table. Errors render the
|
|
122
|
+
server's message with a Retry. Empty-with-filters offers Clear filters.
|
|
123
|
+
|
|
124
|
+
## Search
|
|
125
|
+
|
|
126
|
+
`ExpandingSearch` (exported for reuse): icon at rest → input on focus →
|
|
127
|
+
pinned open while holding text; `/` focuses globally; Escape blurs; 300ms
|
|
128
|
+
debounce, Enter commits immediately.
|