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.
Files changed (369) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +55 -0
  3. package/docs/DetailView.md +173 -0
  4. package/docs/ModelTable.md +128 -0
  5. package/docs/README.md +98 -0
  6. package/docs/admin-assistant.md +72 -0
  7. package/docs/admin-bouncer.md +120 -0
  8. package/docs/admin-cloudwatch.md +38 -0
  9. package/docs/admin-credentials.md +116 -0
  10. package/docs/admin-dashboard.md +15 -0
  11. package/docs/admin-devices-geoip.md +284 -0
  12. package/docs/admin-dns-certificates.md +122 -0
  13. package/docs/admin-dns-records.md +65 -0
  14. package/docs/admin-dns-registrar.md +33 -0
  15. package/docs/admin-dns.md +80 -0
  16. package/docs/admin-identity-users.md +103 -0
  17. package/docs/admin-incidents.md +71 -0
  18. package/docs/admin-jobs.md +426 -0
  19. package/docs/admin-members.md +112 -0
  20. package/docs/admin-messaging.md +56 -0
  21. package/docs/admin-metrics-explorer.md +63 -0
  22. package/docs/admin-monitoring.md +99 -0
  23. package/docs/admin-network-security.md +393 -0
  24. package/docs/admin-phonehub.md +27 -0
  25. package/docs/admin-push.md +36 -0
  26. package/docs/admin-rules.md +87 -0
  27. package/docs/admin-security-tickets.md +40 -0
  28. package/docs/admin-settings.md +78 -0
  29. package/docs/admin-shortlinks.md +32 -0
  30. package/docs/admin-storage.md +141 -0
  31. package/docs/auth-pages.md +212 -0
  32. package/docs/auth.md +58 -0
  33. package/docs/calendar.md +122 -0
  34. package/docs/charts.md +303 -0
  35. package/docs/client.md +206 -0
  36. package/docs/collection-multiselect.md +117 -0
  37. package/docs/collection-select.md +132 -0
  38. package/docs/combobox.md +105 -0
  39. package/docs/dataview.md +226 -0
  40. package/docs/datepicker.md +151 -0
  41. package/docs/daterange.md +170 -0
  42. package/docs/datetimepicker.md +249 -0
  43. package/docs/defineModel.md +72 -0
  44. package/docs/detail-primitives.md +174 -0
  45. package/docs/feedback.md +85 -0
  46. package/docs/file-upload-ux.md +146 -0
  47. package/docs/forms.md +435 -0
  48. package/docs/grouping-and-fmt.md +98 -0
  49. package/docs/idioms.md +112 -0
  50. package/docs/image-editor.md +101 -0
  51. package/docs/loading.md +242 -0
  52. package/docs/location-address.md +76 -0
  53. package/docs/markdown.md +203 -0
  54. package/docs/menus-and-access.md +89 -0
  55. package/docs/multiselect-dropdown.md +153 -0
  56. package/docs/params.md +73 -0
  57. package/docs/popover.md +130 -0
  58. package/docs/realtime.md +61 -0
  59. package/docs/record-feed.md +238 -0
  60. package/docs/right-panel.md +42 -0
  61. package/docs/tabs-and-form-wizard.md +60 -0
  62. package/docs/taginput.md +120 -0
  63. package/docs/timepicker.md +173 -0
  64. package/docs/timezone-select.md +147 -0
  65. package/docs/worldmap.md +302 -0
  66. package/package.json +55 -0
  67. package/src/admin/assistant/AssistantFeed.tsx +29 -0
  68. package/src/admin/assistant/AssistantPanel.tsx +65 -0
  69. package/src/admin/assistant/api.ts +17 -0
  70. package/src/admin/assistant/data.ts +63 -0
  71. package/src/admin/assistant/index.ts +21 -0
  72. package/src/admin/assistant/launchers.tsx +33 -0
  73. package/src/admin/assistant/pages.tsx +78 -0
  74. package/src/admin/assistant/streaming.ts +238 -0
  75. package/src/admin/assistant/types.ts +30 -0
  76. package/src/admin/bouncer/devices.tsx +230 -0
  77. package/src/admin/bouncer/index.ts +34 -0
  78. package/src/admin/bouncer/models.ts +199 -0
  79. package/src/admin/bouncer/signals.tsx +182 -0
  80. package/src/admin/bouncer/signatures.tsx +220 -0
  81. package/src/admin/cloudwatch/CloudWatchChart.tsx +61 -0
  82. package/src/admin/cloudwatch/CloudWatchDashboardPage.tsx +52 -0
  83. package/src/admin/cloudwatch/CloudWatchResourceDetail.tsx +33 -0
  84. package/src/admin/cloudwatch/data.ts +163 -0
  85. package/src/admin/cloudwatch/index.ts +16 -0
  86. package/src/admin/credentials/group-api-keys.tsx +433 -0
  87. package/src/admin/credentials/index.ts +4 -0
  88. package/src/admin/credentials/models.ts +244 -0
  89. package/src/admin/credentials/secret-dialog.tsx +77 -0
  90. package/src/admin/credentials/webhook-subscriptions.tsx +423 -0
  91. package/src/admin/dashboard/AdminDashboardPage.tsx +101 -0
  92. package/src/admin/dashboard/data.ts +60 -0
  93. package/src/admin/dashboard/index.ts +2 -0
  94. package/src/admin/dns/CertificateDetail.tsx +115 -0
  95. package/src/admin/dns/CertificateLifecyclePoller.tsx +141 -0
  96. package/src/admin/dns/CertificateRequestDialog.tsx +103 -0
  97. package/src/admin/dns/CertificatesPage.tsx +85 -0
  98. package/src/admin/dns/DnsRecordEditor.tsx +110 -0
  99. package/src/admin/dns/DnsRecordsPage.tsx +36 -0
  100. package/src/admin/dns/DnsRecordsPanel.tsx +71 -0
  101. package/src/admin/dns/DomainCertificatesSection.tsx +40 -0
  102. package/src/admin/dns/DomainDetail.tsx +41 -0
  103. package/src/admin/dns/DomainOnboardingDialog.tsx +27 -0
  104. package/src/admin/dns/DomainPurchaseDetail.tsx +11 -0
  105. package/src/admin/dns/DomainPurchaseWizard.tsx +112 -0
  106. package/src/admin/dns/DomainPurchasesPage.tsx +16 -0
  107. package/src/admin/dns/DomainsPage.tsx +27 -0
  108. package/src/admin/dns/ProviderCredentialsPage.tsx +304 -0
  109. package/src/admin/dns/RegistrantContactPage.tsx +42 -0
  110. package/src/admin/dns/api.ts +515 -0
  111. package/src/admin/dns/certificate-data.ts +221 -0
  112. package/src/admin/dns/data.ts +32 -0
  113. package/src/admin/dns/dns-data.ts +266 -0
  114. package/src/admin/dns/dns-integration.ts +24 -0
  115. package/src/admin/dns/domain-links.ts +37 -0
  116. package/src/admin/dns/index.ts +86 -0
  117. package/src/admin/dns/models.ts +418 -0
  118. package/src/admin/dns/purchase-data.ts +67 -0
  119. package/src/admin/dns/registrant-data.ts +52 -0
  120. package/src/admin/identity/index.ts +5 -0
  121. package/src/admin/identity/members/GroupMembersPanel.tsx +146 -0
  122. package/src/admin/identity/members/MemberDetail.tsx +226 -0
  123. package/src/admin/identity/members/MembersPage.tsx +144 -0
  124. package/src/admin/identity/members/index.ts +24 -0
  125. package/src/admin/identity/members/member-flows.tsx +132 -0
  126. package/src/admin/identity/members/models.ts +204 -0
  127. package/src/admin/identity/users/UserDetail.tsx +257 -0
  128. package/src/admin/identity/users/UsersPage.tsx +163 -0
  129. package/src/admin/identity/users/index.ts +27 -0
  130. package/src/admin/identity/users/models.ts +232 -0
  131. package/src/admin/identity/users/sections/ApiKeysSection.tsx +119 -0
  132. package/src/admin/identity/users/sections/AuditSection.tsx +190 -0
  133. package/src/admin/identity/users/sections/DevicesSection.tsx +140 -0
  134. package/src/admin/identity/users/sections/GroupsSection.tsx +64 -0
  135. package/src/admin/identity/users/sections/LoginsSection.tsx +106 -0
  136. package/src/admin/identity/users/sections/MetadataPanel.tsx +36 -0
  137. package/src/admin/identity/users/sections/NotificationsSection.tsx +117 -0
  138. package/src/admin/identity/users/sections/OAuthSection.tsx +72 -0
  139. package/src/admin/identity/users/sections/OverviewSection.tsx +109 -0
  140. package/src/admin/identity/users/sections/PermissionsSection.tsx +35 -0
  141. package/src/admin/identity/users/sections/PersonalSection.tsx +73 -0
  142. package/src/admin/identity/users/sections/ProfileSection.tsx +175 -0
  143. package/src/admin/identity/users/sections/SecuritySection.tsx +123 -0
  144. package/src/admin/identity/users/sections/actions.tsx +752 -0
  145. package/src/admin/identity/users/sections/permission-catalog.ts +138 -0
  146. package/src/admin/identity/users/sections/queries.ts +74 -0
  147. package/src/admin/identity/users/sections/shared.tsx +299 -0
  148. package/src/admin/incidents/EventDetail.tsx +43 -0
  149. package/src/admin/incidents/EventsPage.tsx +52 -0
  150. package/src/admin/incidents/IncidentDetail.tsx +68 -0
  151. package/src/admin/incidents/IncidentsPage.tsx +102 -0
  152. package/src/admin/incidents/forensics.tsx +73 -0
  153. package/src/admin/incidents/index.ts +16 -0
  154. package/src/admin/incidents/models.ts +113 -0
  155. package/src/admin/incidents/sanitize.ts +71 -0
  156. package/src/admin/index.ts +383 -0
  157. package/src/admin/jobs/JobDashboardPage.tsx +111 -0
  158. package/src/admin/jobs/JobDetail.tsx +721 -0
  159. package/src/admin/jobs/JobRunnersPage.tsx +216 -0
  160. package/src/admin/jobs/JobsTablePage.tsx +122 -0
  161. package/src/admin/jobs/RunnerDetail.tsx +576 -0
  162. package/src/admin/jobs/ScheduledTaskDetail.tsx +421 -0
  163. package/src/admin/jobs/ScheduledTasksPage.tsx +167 -0
  164. package/src/admin/jobs/columns.tsx +297 -0
  165. package/src/admin/jobs/control.ts +488 -0
  166. package/src/admin/jobs/index.ts +85 -0
  167. package/src/admin/jobs/models.ts +599 -0
  168. package/src/admin/jobs/queries.ts +272 -0
  169. package/src/admin/jobs/scheduled-task-form.tsx +493 -0
  170. package/src/admin/jobs/sections/JobChannelsPanel.tsx +142 -0
  171. package/src/admin/jobs/sections/JobOperationsSection.tsx +423 -0
  172. package/src/admin/jobs/sections/JobRunnersStrip.tsx +82 -0
  173. package/src/admin/jobs/sections/JobThroughputSection.tsx +118 -0
  174. package/src/admin/messaging/EmailDomainWizard.tsx +24 -0
  175. package/src/admin/messaging/EmailDomainsPage.tsx +17 -0
  176. package/src/admin/messaging/EmailTemplatesPage.tsx +13 -0
  177. package/src/admin/messaging/MailboxesPage.tsx +15 -0
  178. package/src/admin/messaging/PublicMessagesPage.tsx +13 -0
  179. package/src/admin/messaging/SandboxedEmailPreview.tsx +23 -0
  180. package/src/admin/messaging/SentMessagesPage.tsx +11 -0
  181. package/src/admin/messaging/api.ts +17 -0
  182. package/src/admin/messaging/data.ts +10 -0
  183. package/src/admin/messaging/index.ts +23 -0
  184. package/src/admin/messaging/models.ts +76 -0
  185. package/src/admin/messaging/push/PushPage.tsx +44 -0
  186. package/src/admin/messaging/push/api.ts +17 -0
  187. package/src/admin/messaging/push/index.ts +9 -0
  188. package/src/admin/messaging/push/models.ts +43 -0
  189. package/src/admin/messaging/sanitize.ts +44 -0
  190. package/src/admin/monitoring/LogInspector.tsx +160 -0
  191. package/src/admin/monitoring/LogsPage.tsx +135 -0
  192. package/src/admin/monitoring/MetricsExplorerPage.tsx +360 -0
  193. package/src/admin/monitoring/MetricsPermissionsPage.tsx +166 -0
  194. package/src/admin/monitoring/MetricsSourcePicker.tsx +136 -0
  195. package/src/admin/monitoring/index.ts +55 -0
  196. package/src/admin/monitoring/metrics-explorer-client.ts +229 -0
  197. package/src/admin/monitoring/metrics-explorer-data.ts +158 -0
  198. package/src/admin/monitoring/models.ts +155 -0
  199. package/src/admin/network/BlockedIPsPage.tsx +194 -0
  200. package/src/admin/network/BlockedIpDetail.tsx +262 -0
  201. package/src/admin/network/FirewallLogPage.tsx +115 -0
  202. package/src/admin/network/IPSetDetail.tsx +280 -0
  203. package/src/admin/network/IPSetEditor.tsx +191 -0
  204. package/src/admin/network/IPSetsPage.tsx +177 -0
  205. package/src/admin/network/geofence/BlocksTab.tsx +289 -0
  206. package/src/admin/network/geofence/ExemptionsTab.tsx +349 -0
  207. package/src/admin/network/geofence/GeofencingPage.tsx +119 -0
  208. package/src/admin/network/geofence/PostureHeader.tsx +184 -0
  209. package/src/admin/network/geofence/RuleEditor.tsx +176 -0
  210. package/src/admin/network/geofence/RulesTab.tsx +254 -0
  211. package/src/admin/network/geofence/SimulatorTab.tsx +281 -0
  212. package/src/admin/network/geofence/geofence-data.ts +674 -0
  213. package/src/admin/network/index.ts +68 -0
  214. package/src/admin/network/models.ts +607 -0
  215. package/src/admin/phonehub/PhoneHubPage.tsx +57 -0
  216. package/src/admin/phonehub/api.ts +47 -0
  217. package/src/admin/phonehub/data.ts +6 -0
  218. package/src/admin/phonehub/index.ts +10 -0
  219. package/src/admin/phonehub/models.ts +68 -0
  220. package/src/admin/rules/HandlerChainBuilder.tsx +51 -0
  221. package/src/admin/rules/RuleSetDetailPage.tsx +33 -0
  222. package/src/admin/rules/RuleSetsPage.tsx +33 -0
  223. package/src/admin/rules/editors.tsx +54 -0
  224. package/src/admin/rules/handler-dsl.ts +213 -0
  225. package/src/admin/rules/index.ts +13 -0
  226. package/src/admin/rules/models.ts +112 -0
  227. package/src/admin/security/devices/LoginEventDetail.tsx +123 -0
  228. package/src/admin/security/devices/LoginLocationMap.tsx +321 -0
  229. package/src/admin/security/devices/LoginLocationsPage.tsx +162 -0
  230. package/src/admin/security/devices/UserDeviceDetail.tsx +546 -0
  231. package/src/admin/security/devices/UserDeviceLocationDetail.tsx +272 -0
  232. package/src/admin/security/devices/UserDevicesPage.tsx +84 -0
  233. package/src/admin/security/devices/index.ts +40 -0
  234. package/src/admin/security/devices/models.ts +470 -0
  235. package/src/admin/security/geoip/GeoIpCachePage.tsx +135 -0
  236. package/src/admin/security/geoip/GeoIpDossier.tsx +804 -0
  237. package/src/admin/security/geoip/geoip-forms.ts +102 -0
  238. package/src/admin/security/geoip/index.ts +27 -0
  239. package/src/admin/security/geoip/models.ts +397 -0
  240. package/src/admin/security/index.ts +31 -0
  241. package/src/admin/security/models.ts +137 -0
  242. package/src/admin/security/tickets.tsx +562 -0
  243. package/src/admin/security-permissions.ts +5 -0
  244. package/src/admin/settings/SettingDetail.tsx +57 -0
  245. package/src/admin/settings/SettingEditor.tsx +157 -0
  246. package/src/admin/settings/SettingsPage.tsx +72 -0
  247. package/src/admin/settings/index.ts +19 -0
  248. package/src/admin/settings/model.ts +143 -0
  249. package/src/admin/shortlinks/ShortlinkDetail.tsx +55 -0
  250. package/src/admin/shortlinks/ShortlinkHistoryPage.tsx +18 -0
  251. package/src/admin/shortlinks/ShortlinksPage.tsx +59 -0
  252. package/src/admin/shortlinks/index.ts +18 -0
  253. package/src/admin/shortlinks/models.ts +246 -0
  254. package/src/admin/storage/BackendsPage.tsx +43 -0
  255. package/src/admin/storage/BucketDetail.tsx +77 -0
  256. package/src/admin/storage/BucketsPage.tsx +64 -0
  257. package/src/admin/storage/FileManagerDetail.tsx +36 -0
  258. package/src/admin/storage/FilePreview.tsx +54 -0
  259. package/src/admin/storage/FileUploadSurface.tsx +250 -0
  260. package/src/admin/storage/FileView.tsx +109 -0
  261. package/src/admin/storage/FilesPage.tsx +53 -0
  262. package/src/admin/storage/file-renditions.ts +99 -0
  263. package/src/admin/storage/index.ts +31 -0
  264. package/src/admin/storage/models.ts +585 -0
  265. package/src/admin/storage/storage-dialogs.tsx +80 -0
  266. package/src/charts/CircularProgress.tsx +281 -0
  267. package/src/charts/KPIStrip.tsx +162 -0
  268. package/src/charts/KPITile.tsx +125 -0
  269. package/src/charts/MetricsChart.tsx +459 -0
  270. package/src/charts/MetricsMiniWidget.tsx +588 -0
  271. package/src/charts/MiniChart.tsx +283 -0
  272. package/src/charts/PieChart.tsx +354 -0
  273. package/src/charts/SeriesChart.tsx +378 -0
  274. package/src/charts/chart-dialogs.tsx +149 -0
  275. package/src/charts/exportChart.ts +144 -0
  276. package/src/charts/index.ts +28 -0
  277. package/src/charts/pie-math.ts +127 -0
  278. package/src/charts/stats.ts +265 -0
  279. package/src/charts/worldmap/WorldMap.tsx +649 -0
  280. package/src/charts/worldmap/countryCentroids.ts +304 -0
  281. package/src/charts/worldmap/geo.ts +293 -0
  282. package/src/charts/worldmap/use-world-land.ts +47 -0
  283. package/src/charts/worldmap/world-land.ts +18 -0
  284. package/src/charts/worldmap/worldmap-data.ts +285 -0
  285. package/src/client/auth.ts +737 -0
  286. package/src/client/client.ts +268 -0
  287. package/src/client/duid.ts +31 -0
  288. package/src/client/errors.ts +33 -0
  289. package/src/client/group-context.ts +28 -0
  290. package/src/client/group.tsx +126 -0
  291. package/src/client/hooks.ts +50 -0
  292. package/src/client/index.ts +37 -0
  293. package/src/client/jwt.ts +75 -0
  294. package/src/client/location.ts +285 -0
  295. package/src/client/lookups.ts +41 -0
  296. package/src/client/markdown-fallback.tsx +115 -0
  297. package/src/client/markdown-parse.ts +386 -0
  298. package/src/client/markdown-sanitize.ts +217 -0
  299. package/src/client/markdown.ts +122 -0
  300. package/src/client/me.ts +188 -0
  301. package/src/client/mock.ts +9984 -0
  302. package/src/client/model.ts +227 -0
  303. package/src/client/params.ts +292 -0
  304. package/src/client/realtime-mock.ts +221 -0
  305. package/src/client/realtime.ts +561 -0
  306. package/src/client/record-feed.ts +329 -0
  307. package/src/client/safe-export.ts +71 -0
  308. package/src/client/types.ts +278 -0
  309. package/src/client/upload.ts +421 -0
  310. package/src/ui/AddressField.tsx +273 -0
  311. package/src/ui/AttachmentQueue.tsx +92 -0
  312. package/src/ui/CollectionMultiSelect.tsx +622 -0
  313. package/src/ui/CollectionSelect.tsx +517 -0
  314. package/src/ui/ComboBox.tsx +336 -0
  315. package/src/ui/DetailView.tsx +438 -0
  316. package/src/ui/FileDrop.tsx +194 -0
  317. package/src/ui/FileField.tsx +369 -0
  318. package/src/ui/FilterBar.tsx +269 -0
  319. package/src/ui/FormFields.tsx +77 -0
  320. package/src/ui/FormView.tsx +360 -0
  321. package/src/ui/FormWizard.tsx +336 -0
  322. package/src/ui/GroupSwitcher.tsx +202 -0
  323. package/src/ui/Guarded.tsx +16 -0
  324. package/src/ui/MarkdownView.tsx +89 -0
  325. package/src/ui/ModelTable.tsx +848 -0
  326. package/src/ui/MultiSelectDropdown.tsx +390 -0
  327. package/src/ui/Popover.tsx +204 -0
  328. package/src/ui/RecordFeed.tsx +627 -0
  329. package/src/ui/RequiresGroup.tsx +32 -0
  330. package/src/ui/RightPanel.tsx +107 -0
  331. package/src/ui/SidebarNav.tsx +330 -0
  332. package/src/ui/Tabs.tsx +161 -0
  333. package/src/ui/TagInput.tsx +381 -0
  334. package/src/ui/ThemeProvider.tsx +52 -0
  335. package/src/ui/UploadQueue.tsx +603 -0
  336. package/src/ui/armed-button.tsx +91 -0
  337. package/src/ui/date/Calendar.tsx +498 -0
  338. package/src/ui/date/DatePicker.tsx +289 -0
  339. package/src/ui/date/DateRangePicker.tsx +280 -0
  340. package/src/ui/date/DateTimePicker.tsx +587 -0
  341. package/src/ui/date/PresetRail.tsx +165 -0
  342. package/src/ui/date/TimePicker.tsx +624 -0
  343. package/src/ui/date/TimezoneSelect.tsx +267 -0
  344. package/src/ui/date/fns.ts +679 -0
  345. package/src/ui/detail/DataView.tsx +692 -0
  346. package/src/ui/detail/FlowStrip.tsx +80 -0
  347. package/src/ui/detail/KnownFieldsCard.tsx +121 -0
  348. package/src/ui/detail/MetadataSection.tsx +219 -0
  349. package/src/ui/detail/StackTraceView.tsx +150 -0
  350. package/src/ui/detail/StatusPanel.tsx +73 -0
  351. package/src/ui/detail/Timeline.tsx +52 -0
  352. package/src/ui/detail/index.ts +12 -0
  353. package/src/ui/field-registry.tsx +428 -0
  354. package/src/ui/field-wire.ts +322 -0
  355. package/src/ui/form-autosave.ts +583 -0
  356. package/src/ui/format.ts +444 -0
  357. package/src/ui/grouping.ts +150 -0
  358. package/src/ui/image-editor/ImageEditor.tsx +484 -0
  359. package/src/ui/image-editor/canvas.ts +254 -0
  360. package/src/ui/image-editor/index.ts +3 -0
  361. package/src/ui/image-editor/math.ts +416 -0
  362. package/src/ui/index.ts +58 -0
  363. package/src/ui/loading.tsx +604 -0
  364. package/src/ui/menu-registry.ts +213 -0
  365. package/src/ui/modal.tsx +297 -0
  366. package/src/ui/password.tsx +302 -0
  367. package/src/ui/schema-form-core.tsx +315 -0
  368. package/src/ui/toast.tsx +219 -0
  369. package/src/ui/ui.tsx +45 -0
@@ -0,0 +1,281 @@
1
+ // SimulatorTab — what-if decisions via `POST /api/geo/simulate`.
2
+ // Port of web-mojo `GeofenceSimulatorView.js`.
3
+ //
4
+ // `GeoFenceEngine.simulate` (engine.py:446-474) deliberately differs from
5
+ // `check`: no bypass shortcut, no cache read or write, no evidence event, and
6
+ // it evaluates even while GEOFENCE_ENABLED is false — which is what makes a
7
+ // staged-rules preview possible. The copy below says exactly that, verbatim
8
+ // from the source, because it is the reason an operator trusts the button.
9
+ //
10
+ // TWO CORRECTIONS over the source:
11
+ // · the "geofencing is currently off" notice reads `decision.enabled`, which
12
+ // `simulate` sets at the TOP LEVEL of the decision. web-mojo read
13
+ // `decision.posture.enabled` — a key the decision has never carried — so
14
+ // the notice never fired once.
15
+ // · the endpoint-scope row shows the scope the form SENT. The decision does
16
+ // not echo it, so reading it back would always render "—".
17
+ import { useMemo, useState, type ReactNode } from 'react';
18
+ import { CollectionSelect, toast } from '../../../ui';
19
+ import { COUNTRY_OPTIONS, countryName } from '../../../charts/worldmap/countryCentroids';
20
+ import { useGeoSimulate } from '../models';
21
+ import {
22
+ ABUSE_FLAGS, US_STATES, buildSimulateBody, collectScopes, describeDecision,
23
+ describeWouldBlock, regionName, scopeLabel,
24
+ type AbuseFlagKey, type GeoDecision, type GeoRulesConfig,
25
+ } from './geofence-data';
26
+
27
+ interface SimForm {
28
+ mode: 'geo' | 'ip';
29
+ country: string;
30
+ state: string;
31
+ ip: string;
32
+ flags: Record<AbuseFlagKey, boolean>;
33
+ group_uuid: string | null;
34
+ scope: string;
35
+ }
36
+
37
+ const EMPTY_FORM: SimForm = {
38
+ mode: 'geo',
39
+ country: '',
40
+ state: '',
41
+ ip: '',
42
+ flags: { vpn: false, tor: false, proxy: false, datacenter: false },
43
+ group_uuid: null,
44
+ scope: '',
45
+ };
46
+
47
+ function MetaRow({ label, value }: { label: string; value: ReactNode }) {
48
+ return <span className="geo-sim-meta-item">{label} <b>{value}</b></span>;
49
+ }
50
+
51
+ function ResultCard({ decision, sentScope }: { decision: GeoDecision; sentScope: string }) {
52
+ const exempt = decision.reason === 'ip_allowlisted';
53
+ const allowed = decision.allowed !== false;
54
+ const tone = exempt ? 'exempt' : allowed ? 'allowed' : 'blocked';
55
+ const icon = exempt ? 'bi-shield-check' : allowed ? 'bi-check-circle' : 'bi-x-circle';
56
+ const wouldLine = describeWouldBlock(decision);
57
+
58
+ const place = [
59
+ decision.region_code ? regionName(decision.region_code) : (decision.region ?? ''),
60
+ decision.country || countryName(decision.country_code),
61
+ ].filter(Boolean).join(', ');
62
+
63
+ return (
64
+ <div className={`geo-sim-result geo-sim-${tone}`}>
65
+ <div className="geo-sim-band">
66
+ <i className={`bi ${icon}`} />
67
+ <span>{describeDecision(decision)}</span>
68
+ </div>
69
+ <div className="geo-sim-body">
70
+ {wouldLine && <p className="geo-sim-would">{wouldLine}</p>}
71
+ {decision.enabled === false && (
72
+ <div className="netsec-note netsec-note-info">
73
+ <i className="bi bi-info-circle" />
74
+ <div>
75
+ Geofencing is currently <b>off</b> — this shows what would happen once it is
76
+ turned on. Simulation evaluates the rules regardless of enforcement.
77
+ </div>
78
+ </div>
79
+ )}
80
+ <div className="geo-sim-meta">
81
+ {place && <MetaRow label="Location" value={place} />}
82
+ {decision.ip && <MetaRow label="IP" value={<code>{decision.ip}</code>} />}
83
+ {decision.rule_level && (
84
+ <MetaRow label="Rule level" value={decision.rule_level === 'system' ? 'Platform' : decision.rule_level} />
85
+ )}
86
+ {/* The decision does not echo `scope`; this is what we SENT. */}
87
+ <MetaRow label="Endpoint scope" value={sentScope ? scopeLabel(sentScope) : 'Any endpoint'} />
88
+ {decision.reason && <MetaRow label="Reason code" value={<code>{decision.reason}</code>} />}
89
+ {decision.strict_posture && <MetaRow label="Strict posture" value="on" />}
90
+ {decision.detail && <MetaRow label="Detail" value={decision.detail} />}
91
+ {exempt && (
92
+ <>
93
+ <MetaRow label="Allowlist source" value={decision.allowlist_source ?? '—'} />
94
+ <MetaRow label="Allowlist reason" value={decision.allowlist_reason ?? '—'} />
95
+ <MetaRow label="Allowlist until" value={decision.allowlist_until ?? 'Never'} />
96
+ </>
97
+ )}
98
+ </div>
99
+ </div>
100
+ </div>
101
+ );
102
+ }
103
+
104
+ export function SimulatorTab({ config }: { config: GeoRulesConfig | null | undefined }) {
105
+ const simulate = useGeoSimulate();
106
+ const [form, setForm] = useState<SimForm>(EMPTY_FORM);
107
+ const [result, setResult] = useState<{ decision: GeoDecision; scope: string } | null>(null);
108
+ const [status, setStatus] = useState<{ text: string; tone: 'muted' | 'bad' }>({ text: '', tone: 'muted' });
109
+
110
+ // Scope options come from the LIVE config — scopes are deployment-defined
111
+ // strings, so the select only exists when this server declares any.
112
+ const scopes = useMemo(() => collectScopes(config), [config]);
113
+
114
+ const run = async () => {
115
+ if (form.mode === 'ip' && !form.ip.trim()) {
116
+ setStatus({ text: 'Enter an IP address to test.', tone: 'bad' });
117
+ return;
118
+ }
119
+ if (form.mode === 'geo' && !form.country) {
120
+ setStatus({ text: 'Pick a country to test.', tone: 'bad' });
121
+ return;
122
+ }
123
+ setStatus({ text: 'Testing…', tone: 'muted' });
124
+ try {
125
+ const decision = await simulate.mutateAsync(buildSimulateBody({
126
+ mode: form.mode,
127
+ ip: form.ip,
128
+ country: form.country,
129
+ state: form.state,
130
+ flags: form.flags,
131
+ group_uuid: form.group_uuid,
132
+ scope: form.scope || null,
133
+ }));
134
+ setResult({ decision, scope: form.scope });
135
+ setStatus({ text: '', tone: 'muted' });
136
+ } catch (err) {
137
+ const message = err instanceof Error ? err.message : 'Simulation failed';
138
+ setStatus({ text: message, tone: 'bad' });
139
+ toast.error(message);
140
+ }
141
+ };
142
+
143
+ return (
144
+ <div className="geo-two-col">
145
+ <div className="panel netsec-card">
146
+ <div className="netsec-card-head"><span>Test a scenario</span></div>
147
+ <div className="netsec-card-body geo-sim-form">
148
+ <div className="field">
149
+ <span className="field-label">Test by</span>
150
+ <div className="seg">
151
+ {([['geo', 'Location'], ['ip', 'IP address']] as const).map(([value, label]) => (
152
+ <button
153
+ key={value}
154
+ type="button"
155
+ className={`seg-btn${form.mode === value ? ' seg-active' : ''}`}
156
+ onClick={() => setForm({ ...form, mode: value })}
157
+ >
158
+ {label}
159
+ </button>
160
+ ))}
161
+ </div>
162
+ </div>
163
+
164
+ {form.mode === 'geo' ? (
165
+ <>
166
+ <label className="field">
167
+ <span className="field-label">Country</span>
168
+ <select
169
+ className="input"
170
+ value={form.country}
171
+ onChange={(e) => setForm({ ...form, country: e.target.value })}
172
+ >
173
+ <option value="">Select a country…</option>
174
+ {COUNTRY_OPTIONS.map((option) => (
175
+ <option key={option.value} value={option.value}>{option.label}</option>
176
+ ))}
177
+ </select>
178
+ </label>
179
+ <label className="field">
180
+ <span className="field-label">US state / region</span>
181
+ <select
182
+ className="input"
183
+ value={form.state}
184
+ onChange={(e) => setForm({ ...form, state: e.target.value })}
185
+ >
186
+ <option value="">— None —</option>
187
+ {US_STATES.map((option) => (
188
+ <option key={option.value} value={option.value}>{option.label}</option>
189
+ ))}
190
+ </select>
191
+ </label>
192
+ <div className="geo-rule-flags">
193
+ <div className="field-label">Appears to be</div>
194
+ {ABUSE_FLAGS.map((flag) => (
195
+ <label key={flag.key} className="switch-setting">
196
+ <span className="field-label">{flag.label}</span>
197
+ <input
198
+ type="checkbox"
199
+ role="switch"
200
+ className="switch"
201
+ checked={form.flags[flag.key]}
202
+ onChange={(e) => setForm({ ...form, flags: { ...form.flags, [flag.key]: e.target.checked } })}
203
+ />
204
+ </label>
205
+ ))}
206
+ </div>
207
+ </>
208
+ ) : (
209
+ <label className="field">
210
+ <span className="field-label">IP address</span>
211
+ <input
212
+ className="input"
213
+ placeholder="203.0.113.7"
214
+ value={form.ip}
215
+ onChange={(e) => setForm({ ...form, ip: e.target.value })}
216
+ />
217
+ <span className="field-help">The IP is resolved and checked against exemptions too.</span>
218
+ </label>
219
+ )}
220
+
221
+ <CollectionSelect
222
+ endpoint="/api/group"
223
+ labelField="name"
224
+ // The engine takes a group UUID, never an id.
225
+ valueField="uuid"
226
+ label="Group (optional)"
227
+ placeholder="Platform rules only"
228
+ value={form.group_uuid}
229
+ onChange={(id) => setForm({ ...form, group_uuid: id == null ? null : String(id) })}
230
+ help="Include a group to test its extra rules. An INACTIVE group is legal and is evaluated — the result says so."
231
+ />
232
+
233
+ {scopes.length > 0 && (
234
+ <label className="field">
235
+ <span className="field-label">Endpoint type</span>
236
+ <select
237
+ className="input"
238
+ value={form.scope}
239
+ onChange={(e) => setForm({ ...form, scope: e.target.value })}
240
+ >
241
+ <option value="">Any endpoint</option>
242
+ {scopes.map((scope) => (
243
+ <option key={scope} value={scope}>{scopeLabel(scope)}</option>
244
+ ))}
245
+ </select>
246
+ <span className="field-help">Options reflect the endpoint types this server actually gates.</span>
247
+ </label>
248
+ )}
249
+
250
+ <div className="geo-save-row">
251
+ <button
252
+ type="button"
253
+ className="btn btn-primary btn-compact"
254
+ disabled={simulate.isPending}
255
+ onClick={() => void run()}
256
+ >
257
+ <i className="bi bi-play-fill" /> Run test
258
+ </button>
259
+ <span className={`geo-status ${status.tone === 'bad' ? 'text-bad' : 'dim'}`}>{status.text}</span>
260
+ </div>
261
+ <p className="dim">
262
+ Tests never affect real traffic, are never cached, and leave no log entries.
263
+ </p>
264
+ </div>
265
+ </div>
266
+
267
+ <div className="geo-side">
268
+ {result
269
+ ? <ResultCard decision={result.decision} sentScope={result.scope} />
270
+ : (
271
+ <div className="panel netsec-card">
272
+ <div className="netsec-card-body dim">
273
+ Run a test to see the decision here. Tests never affect real traffic,
274
+ are never cached, and leave no log entries.
275
+ </div>
276
+ </div>
277
+ )}
278
+ </div>
279
+ </div>
280
+ );
281
+ }