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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// worldmap-data.ts — the WorldMap's data vocabulary and the pure binding
|
|
2
|
+
// helpers that turn django-mojo payloads into markers. No React, no DOM, so
|
|
3
|
+
// every rule here is headless-testable and the three admin consumers
|
|
4
|
+
// (#1291 logins, #1287 geofence, #1292 dashboard geography) share ONE
|
|
5
|
+
// slug-parsing and sizing implementation instead of three.
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
import { COUNTRY_CENTROIDS, type CountryCentroid } from './countryCentroids';
|
|
8
|
+
import type { LatLng } from './geo';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Marker/route colors are TOKENS, resolved by the browser at paint time.
|
|
12
|
+
* A theme flip therefore needs no re-render and no recomputed hexes.
|
|
13
|
+
*
|
|
14
|
+
* `scale` is the value ramp: it replaces web-mojo's hardcoded teal→amber
|
|
15
|
+
* rgba mix (MetricsCountryMapView.js:171-178, duplicated verbatim in
|
|
16
|
+
* LoginLocationMapView.js:283-288) with the same visual gradient expressed
|
|
17
|
+
* in tokens, so it reads correctly in both themes.
|
|
18
|
+
*/
|
|
19
|
+
export const MAP_TONES = {
|
|
20
|
+
ok: 'var(--ok)',
|
|
21
|
+
bad: 'var(--bad)',
|
|
22
|
+
warn: 'var(--warn)',
|
|
23
|
+
info: 'var(--info)',
|
|
24
|
+
accent: 'var(--accent)',
|
|
25
|
+
mute: 'var(--mute)',
|
|
26
|
+
scale: 'var(--ok)',
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export type WorldMapTone = keyof typeof MAP_TONES;
|
|
30
|
+
|
|
31
|
+
const clamp01 = (v: number): number => (Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Tone → a CSS color string. `scale` interpolates `--ok` → `--warn` on
|
|
35
|
+
* `intensity` via `color-mix`; every other tone ignores intensity.
|
|
36
|
+
*
|
|
37
|
+
* An unknown tone (wire data can carry anything) falls back to `accent`
|
|
38
|
+
* WITH a console.warn — never to rendering nothing.
|
|
39
|
+
*/
|
|
40
|
+
export function toneColor(tone: WorldMapTone = 'accent', intensity = 0): string {
|
|
41
|
+
let key = tone;
|
|
42
|
+
if (!(key in MAP_TONES)) {
|
|
43
|
+
console.warn(`WorldMap: unknown tone "${String(tone)}" — falling back to "accent"`);
|
|
44
|
+
key = 'accent';
|
|
45
|
+
}
|
|
46
|
+
if (key !== 'scale') return MAP_TONES[key];
|
|
47
|
+
const okPct = Math.round((1 - clamp01(intensity)) * 100);
|
|
48
|
+
return `color-mix(in srgb, var(--ok) ${okPct}%, var(--warn))`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// REMOVED 2026-08-06: `LOGIN_EVENT_TONES` / `loginEventTone(eventType)`.
|
|
52
|
+
//
|
|
53
|
+
// Ported from web-mojo's rgba palette at LoginLocationMapView.js:31-41, which
|
|
54
|
+
// keyed off a login "event type" (success_login / failed_login / suspicious).
|
|
55
|
+
// Board #1291 then established from django-mojo source that `UserLoginEvent`
|
|
56
|
+
// has NO `event_type` field and never has — so every real row fell through to
|
|
57
|
+
// the grey `mute` default. That is precisely why web-mojo's login map rendered
|
|
58
|
+
// entirely grey, a bug that went unnoticed for years.
|
|
59
|
+
//
|
|
60
|
+
// The working replacement is `loginRiskTone(row)` in
|
|
61
|
+
// `admin/security/devices/models.ts`: danger on `is_new_country`, warning on
|
|
62
|
+
// `is_new_region`, otherwise success — fields the wire actually sends. Do not
|
|
63
|
+
// reintroduce a tone helper here that keys off an event-type vocabulary.
|
|
64
|
+
|
|
65
|
+
/** One plotted point. `T` is the consumer's own row, handed back to handlers. */
|
|
66
|
+
export interface WorldMapMarker<T = unknown> {
|
|
67
|
+
/** Stable React key and the identity used for hover/click bookkeeping. */
|
|
68
|
+
id?: string;
|
|
69
|
+
lat: number;
|
|
70
|
+
lng: number;
|
|
71
|
+
/** Diameter in px. Default 14. */
|
|
72
|
+
size?: number;
|
|
73
|
+
tone?: WorldMapTone;
|
|
74
|
+
/** 0..1 — drives the `scale` tone's ramp. */
|
|
75
|
+
intensity?: number;
|
|
76
|
+
/** Bootstrap icon class drawn inside the dot, e.g. 'bi bi-broadcast-pin'. */
|
|
77
|
+
icon?: string;
|
|
78
|
+
/** Tooltip heading. */
|
|
79
|
+
label?: string;
|
|
80
|
+
/** Numeric value; the default tooltip renders it under the label. */
|
|
81
|
+
value?: number;
|
|
82
|
+
/** Extra tooltip body — a ReactNode SLOT, never an HTML string. */
|
|
83
|
+
detail?: ReactNode;
|
|
84
|
+
/** Legend bucket. Toggling that key in the legend hides this marker. */
|
|
85
|
+
legendKey?: string;
|
|
86
|
+
/** Typed payload for onMarkerClick/onMarkerDoubleClick/onMarkerHover. */
|
|
87
|
+
data?: T;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** An origin→destination line. Endpoint order decides which way the arc bows. */
|
|
91
|
+
export interface WorldMapRoute {
|
|
92
|
+
id?: string;
|
|
93
|
+
from: LatLng;
|
|
94
|
+
to: LatLng;
|
|
95
|
+
/** 0..1 — width, opacity and (for tone 'scale') color all ride this. */
|
|
96
|
+
intensity?: number;
|
|
97
|
+
tone?: WorldMapTone;
|
|
98
|
+
label?: string;
|
|
99
|
+
legendKey?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface WorldMapLegendItem {
|
|
103
|
+
key: string;
|
|
104
|
+
label: string;
|
|
105
|
+
tone?: WorldMapTone;
|
|
106
|
+
/** Explicit swatch color; overrides `tone`. Use a token, not a hex. */
|
|
107
|
+
color?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Injectable basemap geometry ───────────────────────────────────────
|
|
111
|
+
// The component ships NO coastlines (see docs/worldmap.md — that is an open
|
|
112
|
+
// decision). These are the minimal GeoJSON shapes the `land` prop accepts so
|
|
113
|
+
// geometry can be dropped in later without an API change.
|
|
114
|
+
|
|
115
|
+
/** GeoJSON position: [lng, lat] — note the order, opposite of LatLng. */
|
|
116
|
+
export type GeoPosition = [number, number];
|
|
117
|
+
|
|
118
|
+
export interface GeoPolygon {
|
|
119
|
+
type: 'Polygon';
|
|
120
|
+
coordinates: GeoPosition[][];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface GeoMultiPolygon {
|
|
124
|
+
type: 'MultiPolygon';
|
|
125
|
+
coordinates: GeoPosition[][][];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface GeoLandFeature {
|
|
129
|
+
type: 'Feature';
|
|
130
|
+
geometry: GeoPolygon | GeoMultiPolygon | null;
|
|
131
|
+
properties?: Record<string, unknown> | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface GeoLandCollection {
|
|
135
|
+
type: 'FeatureCollection';
|
|
136
|
+
features: GeoLandFeature[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Land geometry: a FeatureCollection, a bare feature array, or a geometry. */
|
|
140
|
+
export type WorldMapLand = GeoLandCollection | readonly GeoLandFeature[] | GeoPolygon | GeoMultiPolygon;
|
|
141
|
+
|
|
142
|
+
/** Normalize any accepted `land` shape into rings of [lng, lat] positions. */
|
|
143
|
+
export function landRings(land: WorldMapLand | null | undefined): GeoPosition[][] {
|
|
144
|
+
if (!land) return [];
|
|
145
|
+
// `'type' in land` rather than Array.isArray: TS does not narrow a
|
|
146
|
+
// ReadonlyArray out of a union via Array.isArray.
|
|
147
|
+
const features: readonly GeoLandFeature[] = 'type' in land
|
|
148
|
+
? (land.type === 'FeatureCollection'
|
|
149
|
+
? (Array.isArray(land.features) ? land.features : [])
|
|
150
|
+
: [{ type: 'Feature', geometry: land }])
|
|
151
|
+
: land;
|
|
152
|
+
|
|
153
|
+
const rings: GeoPosition[][] = [];
|
|
154
|
+
let skipped = 0;
|
|
155
|
+
for (const feature of features) {
|
|
156
|
+
const geometry = feature?.geometry;
|
|
157
|
+
if (!geometry) { skipped += 1; continue; }
|
|
158
|
+
if (geometry.type === 'Polygon') {
|
|
159
|
+
for (const ring of geometry.coordinates) rings.push(ring);
|
|
160
|
+
} else if (geometry.type === 'MultiPolygon') {
|
|
161
|
+
for (const polygon of geometry.coordinates) for (const ring of polygon) rings.push(ring);
|
|
162
|
+
} else {
|
|
163
|
+
skipped += 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (skipped > 0) {
|
|
167
|
+
console.warn(`WorldMap: skipped ${skipped} land feature(s) — only Polygon and MultiPolygon geometries are drawn`);
|
|
168
|
+
}
|
|
169
|
+
return rings;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Sizing + slug binding ─────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Value → marker diameter. Default range [18, 42] reproduces web-mojo's
|
|
176
|
+
* `Math.round(18 + intensity * 24)` exactly (LoginLocationMapView used
|
|
177
|
+
* `* 26`; pass `[18, 44]` for that). A non-positive or non-finite max means
|
|
178
|
+
* "no scale yet" and returns the minimum rather than NaN.
|
|
179
|
+
*/
|
|
180
|
+
export function scaleMarkerSize(
|
|
181
|
+
value: number,
|
|
182
|
+
max: number,
|
|
183
|
+
range: readonly [number, number] = [18, 42],
|
|
184
|
+
scale: 'linear' | 'sqrt' = 'linear',
|
|
185
|
+
): number {
|
|
186
|
+
const [min, top] = range;
|
|
187
|
+
if (!Number.isFinite(max) || max <= 0 || !Number.isFinite(value)) return Math.round(min);
|
|
188
|
+
const i = clamp01(value / max);
|
|
189
|
+
const eased = scale === 'sqrt' ? Math.sqrt(i) : i;
|
|
190
|
+
return Math.round(min + eased * (top - min));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Metrics key → ISO2 country code, or null.
|
|
195
|
+
*
|
|
196
|
+
* ONE rule covers both shapes found in the wild: bare codes
|
|
197
|
+
* (MetricsCountryMapView.js:107-118 treats each key of `data.data` as an ISO
|
|
198
|
+
* code) and namespaced slugs (`incident_events:country:US`,
|
|
199
|
+
* `firewall:blocks:country:US`, `bouncer:blocks:country:US` —
|
|
200
|
+
* GeographyPanel.js:232-243 takes the trailing segment). Uppercase the last
|
|
201
|
+
* `:`-separated segment; accept it only if it is two characters AND a known
|
|
202
|
+
* country. `api_calls` and `ZZ` therefore both return null.
|
|
203
|
+
*/
|
|
204
|
+
export function countryCodeFromSlug(slug: string | null | undefined): string | null {
|
|
205
|
+
if (!slug) return null;
|
|
206
|
+
const last = String(slug).split(':').pop()?.trim().toUpperCase();
|
|
207
|
+
if (!last || last.length !== 2) return null;
|
|
208
|
+
return last in COUNTRY_CENTROIDS ? last : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** One country's rolled-up series — the payload markers carry as `data`. */
|
|
212
|
+
export interface CountryTotal {
|
|
213
|
+
code: string;
|
|
214
|
+
name: string;
|
|
215
|
+
centroid: CountryCentroid;
|
|
216
|
+
total: number;
|
|
217
|
+
values: number[];
|
|
218
|
+
/** total / the top country's total, 0..1. */
|
|
219
|
+
intensity: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface CountrySeriesOptions {
|
|
223
|
+
/** Top-N cut. Source default: MetricsCountryMapView's `maxCountries: 12`. */
|
|
224
|
+
maxCountries?: number;
|
|
225
|
+
tone?: WorldMapTone;
|
|
226
|
+
sizeRange?: readonly [number, number];
|
|
227
|
+
sizeScale?: 'linear' | 'sqrt';
|
|
228
|
+
/** Noun in the default tooltip line, e.g. "12,043 Events". */
|
|
229
|
+
metricLabel?: string;
|
|
230
|
+
/** Legend bucket for every produced marker. */
|
|
231
|
+
legendKey?: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The shared binding: a metrics `data.data` map → sorted, top-N,
|
|
236
|
+
* centroid-joined markers. Keys may be bare ISO2 or namespaced slugs; values
|
|
237
|
+
* may be a series array or a single number. Rows that sum to zero, or whose
|
|
238
|
+
* code is not a known country, are dropped — exactly what the source did,
|
|
239
|
+
* only once instead of in each consumer.
|
|
240
|
+
*/
|
|
241
|
+
export function countrySeriesToMarkers(
|
|
242
|
+
series: Readonly<Record<string, readonly number[] | number | null | undefined>>,
|
|
243
|
+
opts: CountrySeriesOptions = {},
|
|
244
|
+
): WorldMapMarker<CountryTotal>[] {
|
|
245
|
+
const {
|
|
246
|
+
maxCountries = 12,
|
|
247
|
+
tone = 'scale',
|
|
248
|
+
sizeRange = [18, 42],
|
|
249
|
+
sizeScale = 'linear',
|
|
250
|
+
metricLabel = 'Events',
|
|
251
|
+
legendKey,
|
|
252
|
+
} = opts;
|
|
253
|
+
|
|
254
|
+
const rows: Omit<CountryTotal, 'intensity'>[] = [];
|
|
255
|
+
for (const [key, raw] of Object.entries(series ?? {})) {
|
|
256
|
+
const code = countryCodeFromSlug(key);
|
|
257
|
+
if (!code) continue;
|
|
258
|
+
const values = Array.isArray(raw) ? raw.map((v) => Number(v) || 0) : [Number(raw) || 0];
|
|
259
|
+
const total = values.reduce((sum, v) => sum + v, 0);
|
|
260
|
+
if (!(total > 0)) continue;
|
|
261
|
+
const centroid = COUNTRY_CENTROIDS[code]!;
|
|
262
|
+
rows.push({ code, name: centroid.name, centroid, total, values });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
rows.sort((a, b) => b.total - a.total);
|
|
266
|
+
const top = rows.slice(0, Math.max(0, maxCountries));
|
|
267
|
+
const max = top[0]?.total ?? 0;
|
|
268
|
+
|
|
269
|
+
return top.map((row) => {
|
|
270
|
+
const data: CountryTotal = { ...row, intensity: max > 0 ? row.total / max : 0 };
|
|
271
|
+
return {
|
|
272
|
+
id: row.code,
|
|
273
|
+
lat: row.centroid.lat,
|
|
274
|
+
lng: row.centroid.lng,
|
|
275
|
+
size: scaleMarkerSize(row.total, max, sizeRange, sizeScale),
|
|
276
|
+
tone,
|
|
277
|
+
intensity: data.intensity,
|
|
278
|
+
label: row.name,
|
|
279
|
+
value: row.total,
|
|
280
|
+
detail: `${row.total.toLocaleString()} ${metricLabel}`,
|
|
281
|
+
...(legendKey ? { legendKey } : {}),
|
|
282
|
+
data,
|
|
283
|
+
} satisfies WorldMapMarker<CountryTotal>;
|
|
284
|
+
});
|
|
285
|
+
}
|