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,176 @@
|
|
|
1
|
+
// GeofenceRuleEditor — the ONE geofence rule editor, shared by the platform
|
|
2
|
+
// page (RulesTab) and the group-scoped panel (apps/portal GeofenceSection).
|
|
3
|
+
// Port of web-mojo `GeofenceRuleForm.buildRuleFields` + the guided↔JSON mode
|
|
4
|
+
// machinery in `GeofenceRulesView`.
|
|
5
|
+
//
|
|
6
|
+
// Two scopes, one projection: the group surface can only TIGHTEN and writes
|
|
7
|
+
// `metadata.geofence` through the group REST (merge-safe, via
|
|
8
|
+
// `buildGroupRulePayload`); the platform surface writes `POST /api/geo/rules`
|
|
9
|
+
// (a full replace). Neither can write the other's rule — but both speak the
|
|
10
|
+
// same DSL through the same lossy projection, which is exactly why the
|
|
11
|
+
// projection lives in one module and the editor in one component.
|
|
12
|
+
import { MultiSelectDropdown, toast } from '../../../ui';
|
|
13
|
+
import {
|
|
14
|
+
ABUSE_FLAGS, COUNTRY_MODE_OPTS, COUNTRY_OPTIONS, EMPTY_RULE_FORM, US_STATES,
|
|
15
|
+
coerceRuleInput, formToRule, isAdvancedRule, ruleToForm,
|
|
16
|
+
type GeofenceRule, type RuleForm,
|
|
17
|
+
} from './geofence-data';
|
|
18
|
+
|
|
19
|
+
/** The editor is fully controlled; this is its entire state. */
|
|
20
|
+
export interface RuleEditorValue {
|
|
21
|
+
mode: 'guided' | 'json';
|
|
22
|
+
form: RuleForm;
|
|
23
|
+
json: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Baseline the editor from a stored rule. A rule the guided form cannot
|
|
28
|
+
* represent opens in JSON — never silently rewritten into a lossy form.
|
|
29
|
+
*/
|
|
30
|
+
export function makeRuleEditorValue(rule: unknown): RuleEditorValue {
|
|
31
|
+
const forced = isAdvancedRule(rule);
|
|
32
|
+
return {
|
|
33
|
+
mode: forced ? 'json' : 'guided',
|
|
34
|
+
form: forced ? { ...EMPTY_RULE_FORM, countries: [], blocked_states: [] } : ruleToForm(rule),
|
|
35
|
+
json: JSON.stringify(rule ?? {}, null, 2),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The rule the editor currently holds, or null when the JSON is invalid. */
|
|
40
|
+
export function ruleFromEditorValue(value: RuleEditorValue): GeofenceRule | null {
|
|
41
|
+
return value.mode === 'json' ? coerceRuleInput(value.json) : formToRule(value.form);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const TOGGLE_JSON_ERROR = 'Fix the JSON before switching back to the guided editor.';
|
|
45
|
+
const TOGGLE_SHAPE_ERROR = "This rule uses options the guided editor can't represent.";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Switch modes, carrying unsaved edits across. Returns the next value, or an
|
|
49
|
+
* error message when the JSON→guided direction must be refused.
|
|
50
|
+
*/
|
|
51
|
+
export function toggleRuleEditorMode(value: RuleEditorValue): RuleEditorValue | { error: string } {
|
|
52
|
+
if (value.mode === 'json') {
|
|
53
|
+
const rule = coerceRuleInput(value.json);
|
|
54
|
+
if (rule === null) return { error: TOGGLE_JSON_ERROR };
|
|
55
|
+
if (isAdvancedRule(rule)) return { error: TOGGLE_SHAPE_ERROR };
|
|
56
|
+
return { mode: 'guided', form: ruleToForm(rule), json: value.json };
|
|
57
|
+
}
|
|
58
|
+
// Guided → JSON: serialize the LIVE form, so nothing typed is lost.
|
|
59
|
+
const rule = formToRule(value.form);
|
|
60
|
+
return { mode: 'json', form: value.form, json: JSON.stringify(rule, null, 2) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function GeofenceRuleEditor({
|
|
64
|
+
value, onChange, advancedForced = false, disabled = false, jsonHelp,
|
|
65
|
+
}: {
|
|
66
|
+
value: RuleEditorValue;
|
|
67
|
+
onChange: (next: RuleEditorValue) => void;
|
|
68
|
+
/**
|
|
69
|
+
* The STORED rule is unrepresentable, so the guided form is not offered at
|
|
70
|
+
* all (no toggle) — flipping to it would silently drop clauses.
|
|
71
|
+
*/
|
|
72
|
+
advancedForced?: boolean;
|
|
73
|
+
disabled?: boolean;
|
|
74
|
+
jsonHelp?: string;
|
|
75
|
+
}) {
|
|
76
|
+
const setForm = (form: RuleForm) => onChange({ ...value, form });
|
|
77
|
+
|
|
78
|
+
const onToggle = () => {
|
|
79
|
+
const next = toggleRuleEditorMode(value);
|
|
80
|
+
if ('error' in next) { toast.error(next.error); return; }
|
|
81
|
+
onChange(next);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="geo-rule-editor">
|
|
86
|
+
{advancedForced && (
|
|
87
|
+
<div className="netsec-note netsec-note-warn">
|
|
88
|
+
<i className="bi bi-braces" />
|
|
89
|
+
<div>
|
|
90
|
+
These rules use options the guided editor can’t represent, so they’re shown
|
|
91
|
+
as JSON. Edits are still validated by the server on save.
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{value.mode === 'json' ? (
|
|
97
|
+
<label className="field">
|
|
98
|
+
<span className="field-label">Rule (JSON)</span>
|
|
99
|
+
<textarea
|
|
100
|
+
className="input geo-rule-json"
|
|
101
|
+
rows={12}
|
|
102
|
+
spellCheck={false}
|
|
103
|
+
disabled={disabled}
|
|
104
|
+
value={value.json}
|
|
105
|
+
onChange={(e) => onChange({ ...value, json: e.target.value })}
|
|
106
|
+
/>
|
|
107
|
+
<span className="field-help">
|
|
108
|
+
{jsonHelp ?? 'Top-level keys: country, region, abuse. Operators: in, not_in, eq. Abuse flags: false blocks when detected, true requires the flag.'}
|
|
109
|
+
</span>
|
|
110
|
+
</label>
|
|
111
|
+
) : (
|
|
112
|
+
<div className="geo-rule-form">
|
|
113
|
+
<label className="field">
|
|
114
|
+
<span className="field-label">Country policy</span>
|
|
115
|
+
<select
|
|
116
|
+
className="input"
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
value={value.form.country_mode}
|
|
119
|
+
onChange={(e) => setForm({ ...value.form, country_mode: e.target.value as RuleForm['country_mode'] })}
|
|
120
|
+
>
|
|
121
|
+
{COUNTRY_MODE_OPTS.map((option) => (
|
|
122
|
+
<option key={option.value} value={option.value}>{option.label}</option>
|
|
123
|
+
))}
|
|
124
|
+
</select>
|
|
125
|
+
</label>
|
|
126
|
+
|
|
127
|
+
{value.form.country_mode !== '' && (
|
|
128
|
+
<MultiSelectDropdown
|
|
129
|
+
label="Countries"
|
|
130
|
+
help="Countries are matched by ISO code; pick them by name."
|
|
131
|
+
options={[...COUNTRY_OPTIONS]}
|
|
132
|
+
value={value.form.countries}
|
|
133
|
+
onChange={(values) => setForm({ ...value.form, countries: values.map(String) })}
|
|
134
|
+
placeholder="Pick countries…"
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
<MultiSelectDropdown
|
|
139
|
+
label="Blocked US states"
|
|
140
|
+
help="States are matched by region code (Washington → US-WA)."
|
|
141
|
+
options={US_STATES}
|
|
142
|
+
value={value.form.blocked_states}
|
|
143
|
+
onChange={(values) => setForm({ ...value.form, blocked_states: values.map(String) })}
|
|
144
|
+
placeholder="No blocked states"
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
<div className="geo-rule-flags">
|
|
148
|
+
<div className="field-label">Anonymized connections</div>
|
|
149
|
+
{ABUSE_FLAGS.map((flag) => (
|
|
150
|
+
<label key={flag.key} className="switch-setting">
|
|
151
|
+
<span className="field-label">
|
|
152
|
+
Block {flag.label.toLowerCase()}
|
|
153
|
+
<span className="field-help" style={{ display: 'block' }}>{flag.help}</span>
|
|
154
|
+
</span>
|
|
155
|
+
<input
|
|
156
|
+
type="checkbox"
|
|
157
|
+
role="switch"
|
|
158
|
+
className="switch"
|
|
159
|
+
disabled={disabled}
|
|
160
|
+
checked={value.form[`block_${flag.key}`]}
|
|
161
|
+
onChange={(e) => setForm({ ...value.form, [`block_${flag.key}`]: e.target.checked })}
|
|
162
|
+
/>
|
|
163
|
+
</label>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{!advancedForced && (
|
|
170
|
+
<button type="button" className="btn-link geo-rule-toggle" disabled={disabled} onClick={onToggle}>
|
|
171
|
+
{value.mode === 'json' ? 'Back to the guided editor' : 'Edit as JSON (advanced)'}
|
|
172
|
+
</button>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// RulesTab — the platform rules editor and the current-policy read-out.
|
|
2
|
+
// Port of web-mojo `GeofenceRulesView.js`.
|
|
3
|
+
//
|
|
4
|
+
// Rules are written ONLY through `POST /api/geo/rules`, which is
|
|
5
|
+
// server-validated by `services/geofence/dsl.validate_rule` and answers a
|
|
6
|
+
// human-readable 400 ("geofence rule: 'country' has unknown operator 'bogus';
|
|
7
|
+
// valid operators are ['eq','in','not_in']"). The generic Settings editor
|
|
8
|
+
// would bypass that validation entirely, which is why it is never offered.
|
|
9
|
+
//
|
|
10
|
+
// A save is a FULL REPLACE that takes effect fleet-wide immediately and
|
|
11
|
+
// invalidates cached decisions — so it is an ArmedButton whose armed label
|
|
12
|
+
// carries the plain-language diff of the clauses being added and removed.
|
|
13
|
+
import { useState } from 'react';
|
|
14
|
+
import { ArmedButton, Badge, fmt, toast } from '../../../ui';
|
|
15
|
+
import { useCan } from '../../../client';
|
|
16
|
+
import type { EventRow } from '../../incidents/models';
|
|
17
|
+
import {
|
|
18
|
+
GEOFENCE_EVENT_CATEGORIES, GeofenceEventModel, configChangedBy,
|
|
19
|
+
useRemoveGeoRulesOverride, useSaveGeoRules,
|
|
20
|
+
} from '../models';
|
|
21
|
+
import {
|
|
22
|
+
GEOFENCE_MANAGE_PERMS, SECURITY_EVENTS_PERMS, describeRule, diffRules, isAdvancedRule,
|
|
23
|
+
type GeoRulesConfig, type GeofenceRule, type RuleClause,
|
|
24
|
+
} from './geofence-data';
|
|
25
|
+
import {
|
|
26
|
+
GeofenceRuleEditor, makeRuleEditorValue, ruleFromEditorValue, type RuleEditorValue,
|
|
27
|
+
} from './RuleEditor';
|
|
28
|
+
|
|
29
|
+
const CLAUSE_ICONS: Record<RuleClause['tone'], string> = {
|
|
30
|
+
block: 'bi-slash-circle',
|
|
31
|
+
warn: 'bi-exclamation-triangle',
|
|
32
|
+
ok: 'bi-check-circle',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function ClauseRow({ clause }: { clause: RuleClause }) {
|
|
36
|
+
return (
|
|
37
|
+
<div className={`geo-clause geo-clause-${clause.tone}`}>
|
|
38
|
+
<i className={`bi ${CLAUSE_ICONS[clause.tone]}`} />
|
|
39
|
+
<span>{clause.text}</span>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The armed label: what this replace ADDS and REMOVES, in plain language. */
|
|
45
|
+
export function armedSaveLabel(oldRule: unknown, newRule: unknown): string {
|
|
46
|
+
const { added, removed } = diffRules(oldRule, newRule);
|
|
47
|
+
if (added.length === 0 && removed.length === 0) {
|
|
48
|
+
return 'Click again — replaces the platform rule fleet-wide (no visible clause changes)';
|
|
49
|
+
}
|
|
50
|
+
const parts: string[] = [];
|
|
51
|
+
if (added.length) parts.push(`adds ${added.length} clause${added.length === 1 ? '' : 's'}`);
|
|
52
|
+
if (removed.length) parts.push(`removes ${removed.length} clause${removed.length === 1 ? '' : 's'}`);
|
|
53
|
+
return `Click again — live fleet-wide immediately: ${parts.join(', ')}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ChangeHistory() {
|
|
57
|
+
const { can } = useCan(SECURITY_EVENTS_PERMS);
|
|
58
|
+
// `enabled: can` — a geofence-only operator issues NO request here.
|
|
59
|
+
const query = GeofenceEventModel.useList(
|
|
60
|
+
{ category: GEOFENCE_EVENT_CATEGORIES.config, size: 10, sort: '-created' },
|
|
61
|
+
{ enabled: can },
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (!can) {
|
|
65
|
+
return (
|
|
66
|
+
<p className="dim">
|
|
67
|
+
<i className="bi bi-eye-slash" /> Change history reads security events — it requires
|
|
68
|
+
security-events access (view_security).
|
|
69
|
+
</p>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (query.isPending) return <p className="dim">Loading change history…</p>;
|
|
73
|
+
if (query.error) return <p className="text-bad">{query.error.message}</p>;
|
|
74
|
+
|
|
75
|
+
const rows = query.data?.rows ?? [];
|
|
76
|
+
if (rows.length === 0) return <p className="dim">No changes recorded.</p>;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<ul className="geo-history">
|
|
80
|
+
{rows.map((event: EventRow) => (
|
|
81
|
+
<li key={event.id}>
|
|
82
|
+
<span className="geo-history-when" title={fmt.datetime(event.created)}>
|
|
83
|
+
{fmt.relative(event.created)}
|
|
84
|
+
</span>
|
|
85
|
+
<span className="geo-history-who">{configChangedBy(event.metadata) || '—'}</span>
|
|
86
|
+
<span className="geo-history-what">{event.title || event.details || 'Configuration changed'}</span>
|
|
87
|
+
</li>
|
|
88
|
+
))}
|
|
89
|
+
</ul>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function RulesTab({ config }: { config: GeoRulesConfig | null | undefined }) {
|
|
94
|
+
const { can: canManage } = useCan(GEOFENCE_MANAGE_PERMS);
|
|
95
|
+
const save = useSaveGeoRules();
|
|
96
|
+
const removeOverride = useRemoveGeoRulesOverride();
|
|
97
|
+
|
|
98
|
+
const storedRule: GeofenceRule = (config?.system?.rule ?? {}) as GeofenceRule;
|
|
99
|
+
const source = config?.system?.source || 'none';
|
|
100
|
+
const advancedForced = isAdvancedRule(storedRule);
|
|
101
|
+
|
|
102
|
+
// Re-baseline whenever the STORED rule changes identity (post-save
|
|
103
|
+
// refetch). A live form must never be rebuilt under the operator.
|
|
104
|
+
const baselineKey = JSON.stringify(storedRule);
|
|
105
|
+
const [state, setState] = useState<{ key: string; value: RuleEditorValue }>(() => ({
|
|
106
|
+
key: baselineKey,
|
|
107
|
+
value: makeRuleEditorValue(storedRule),
|
|
108
|
+
}));
|
|
109
|
+
if (state.key !== baselineKey) {
|
|
110
|
+
setState({ key: baselineKey, value: makeRuleEditorValue(storedRule) });
|
|
111
|
+
}
|
|
112
|
+
const [status, setStatus] = useState<{ text: string; tone: 'muted' | 'ok' | 'bad' }>({ text: '', tone: 'muted' });
|
|
113
|
+
|
|
114
|
+
const draftRule = ruleFromEditorValue(state.value);
|
|
115
|
+
const clauses = describeRule(storedRule);
|
|
116
|
+
|
|
117
|
+
const onSave = async () => {
|
|
118
|
+
if (draftRule === null) {
|
|
119
|
+
const message = 'Not saved — the rule must be a valid JSON object.';
|
|
120
|
+
setStatus({ text: message, tone: 'bad' });
|
|
121
|
+
toast.error(message);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setStatus({ text: 'Saving…', tone: 'muted' });
|
|
125
|
+
try {
|
|
126
|
+
await save.mutateAsync(draftRule);
|
|
127
|
+
setStatus({ text: 'Saved — rules are live.', tone: 'ok' });
|
|
128
|
+
toast.success('Platform rules saved');
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// The DSL validator's own message, inline AND as a toast.
|
|
131
|
+
const message = err instanceof Error ? err.message : 'Failed to save rules';
|
|
132
|
+
setStatus({ text: message, tone: 'bad' });
|
|
133
|
+
toast.error(message);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const onRemoveOverride = async () => {
|
|
138
|
+
try {
|
|
139
|
+
await removeOverride.mutateAsync();
|
|
140
|
+
setStatus({ text: 'Portal override removed.', tone: 'ok' });
|
|
141
|
+
toast.success('Portal override removed');
|
|
142
|
+
} catch (err) {
|
|
143
|
+
const message = err instanceof Error ? err.message : 'Failed to remove the override';
|
|
144
|
+
setStatus({ text: message, tone: 'bad' });
|
|
145
|
+
toast.error(message);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className="geo-two-col">
|
|
151
|
+
<div className="panel netsec-card">
|
|
152
|
+
<div className="netsec-card-head">
|
|
153
|
+
<span>Edit platform rules</span>
|
|
154
|
+
<span className="eyebrow">applies to every group</span>
|
|
155
|
+
</div>
|
|
156
|
+
<div className="netsec-card-body">
|
|
157
|
+
{!canManage ? (
|
|
158
|
+
<p className="dim">
|
|
159
|
+
<i className="bi bi-eye" /> You have read-only access to geofencing.
|
|
160
|
+
Editing platform rules requires the manage-geofence permission.
|
|
161
|
+
</p>
|
|
162
|
+
) : (
|
|
163
|
+
<>
|
|
164
|
+
{source === 'conf' && (
|
|
165
|
+
<div className="netsec-note netsec-note-info">
|
|
166
|
+
<i className="bi bi-lock" />
|
|
167
|
+
<div>
|
|
168
|
+
These rules come from the <b>deployment file</b>. Saving here creates a
|
|
169
|
+
portal-managed override that takes precedence; removing the override
|
|
170
|
+
falls back to the deploy-file rules.
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
<GeofenceRuleEditor
|
|
176
|
+
value={state.value}
|
|
177
|
+
onChange={(value) => setState({ key: baselineKey, value })}
|
|
178
|
+
advancedForced={advancedForced}
|
|
179
|
+
disabled={save.isPending}
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
<div className="geo-save-row">
|
|
183
|
+
<ArmedButton
|
|
184
|
+
className="btn-primary btn-compact"
|
|
185
|
+
icon="bi-check-lg"
|
|
186
|
+
label="Save rules"
|
|
187
|
+
armedLabel={armedSaveLabel(storedRule, draftRule ?? {})}
|
|
188
|
+
disabled={save.isPending}
|
|
189
|
+
onConfirm={onSave}
|
|
190
|
+
/>
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
className="btn btn-compact"
|
|
194
|
+
disabled={save.isPending}
|
|
195
|
+
onClick={() => {
|
|
196
|
+
setState({ key: baselineKey, value: makeRuleEditorValue(storedRule) });
|
|
197
|
+
setStatus({ text: '', tone: 'muted' });
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
Discard changes
|
|
201
|
+
</button>
|
|
202
|
+
<span className={`geo-status ${status.tone === 'bad' ? 'text-bad' : status.tone === 'ok' ? 'text-ok' : 'dim'}`}>
|
|
203
|
+
{status.text}
|
|
204
|
+
</span>
|
|
205
|
+
{source === 'setting' && (
|
|
206
|
+
<ArmedButton
|
|
207
|
+
className="btn-compact btn-danger geo-remove-override"
|
|
208
|
+
icon="bi-x-circle"
|
|
209
|
+
label="Remove portal override"
|
|
210
|
+
armedLabel="Click again — the server falls back to the deploy-file rules (or none) and cached decisions are invalidated"
|
|
211
|
+
disabled={removeOverride.isPending}
|
|
212
|
+
onConfirm={onRemoveOverride}
|
|
213
|
+
/>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
</>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div className="geo-side">
|
|
222
|
+
<div className="panel netsec-card">
|
|
223
|
+
<div className="netsec-card-head">
|
|
224
|
+
<span>Current policy</span>
|
|
225
|
+
<span className="eyebrow">plain language</span>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="netsec-card-body">
|
|
228
|
+
{clauses.length > 0
|
|
229
|
+
? clauses.map((clause, index) => <ClauseRow key={index} clause={clause} />)
|
|
230
|
+
: <p className="dim">No rules configured — all locations are allowed.</p>}
|
|
231
|
+
<div className="geo-clause geo-clause-note">
|
|
232
|
+
<i className="bi bi-plus-circle" />
|
|
233
|
+
<span>Groups may add stricter rules of their own on top. They can never loosen this floor.</span>
|
|
234
|
+
</div>
|
|
235
|
+
{config?.system?.modified && (
|
|
236
|
+
<p className="dim geo-modified">
|
|
237
|
+
Last written {fmt.datetime(config.system.modified)}
|
|
238
|
+
{' '}<Badge tone="muted">{source}</Badge>
|
|
239
|
+
</p>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div className="panel netsec-card">
|
|
245
|
+
<div className="netsec-card-head"><span>Change history</span></div>
|
|
246
|
+
<div className="netsec-card-body">
|
|
247
|
+
<ChangeHistory />
|
|
248
|
+
<p className="dim geo-history-foot">Every save is recorded with who and when.</p>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|