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,86 @@
|
|
|
1
|
+
import type { AdminSection } from '../index';
|
|
2
|
+
import { ProviderCredentialsPage } from './ProviderCredentialsPage';
|
|
3
|
+
import { DomainsPage } from './DomainsPage';
|
|
4
|
+
import { DnsRecordsPage } from './DnsRecordsPage';
|
|
5
|
+
import { CertificatesPage } from './CertificatesPage';
|
|
6
|
+
import { DomainPurchasesPage } from './DomainPurchasesPage';
|
|
7
|
+
import { RegistrantContactPage } from './RegistrantContactPage';
|
|
8
|
+
import { resolveDnsDomainByName } from './api';
|
|
9
|
+
import { registerDnsAdminIntegration } from './dns-integration';
|
|
10
|
+
import { DNS_MANAGE_PERMISSIONS, DNS_VIEW_PERMISSIONS } from './models';
|
|
11
|
+
import { registerDnsDomainLinks } from './domain-links';
|
|
12
|
+
|
|
13
|
+
export * from './models';
|
|
14
|
+
export * from './api';
|
|
15
|
+
export * from './data';
|
|
16
|
+
export * from './ProviderCredentialsPage';
|
|
17
|
+
export * from './dns-data';
|
|
18
|
+
export * from './dns-integration';
|
|
19
|
+
export * from './domain-links';
|
|
20
|
+
export * from './DomainsPage';
|
|
21
|
+
export * from './DomainDetail';
|
|
22
|
+
export * from './DnsRecordsPage';
|
|
23
|
+
export * from './DnsRecordsPanel';
|
|
24
|
+
export * from './DnsRecordEditor';
|
|
25
|
+
export * from './certificate-data';
|
|
26
|
+
export * from './CertificateLifecyclePoller';
|
|
27
|
+
export * from './CertificatesPage';
|
|
28
|
+
export * from './CertificateDetail';
|
|
29
|
+
export * from './CertificateRequestDialog';
|
|
30
|
+
export * from './DomainCertificatesSection';
|
|
31
|
+
export * from './purchase-data';
|
|
32
|
+
export * from './registrant-data';
|
|
33
|
+
export * from './DomainPurchaseWizard';
|
|
34
|
+
export * from './DomainOnboardingDialog';
|
|
35
|
+
export * from './DomainPurchaseDetail';
|
|
36
|
+
export * from './DomainPurchasesPage';
|
|
37
|
+
export * from './RegistrantContactPage';
|
|
38
|
+
|
|
39
|
+
registerDnsAdminIntegration({
|
|
40
|
+
resolveDomainByName: resolveDnsDomainByName,
|
|
41
|
+
recordsHref: (domainId) => `dns/records?domain=${encodeURIComponent(domainId)}`,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
registerDnsDomainLinks(
|
|
45
|
+
{ key: 'domains', label: 'Domains', icon: 'bi-globe2', route: 'domains' },
|
|
46
|
+
{ key: 'records', label: 'DNS Records', icon: 'bi-list-columns', route: (domain) => `records?domain=${encodeURIComponent(domain.id)}` },
|
|
47
|
+
{ key: 'certificates', label: 'Certificates', icon: 'bi-patch-check', route: (domain) => `certificates?domain__exact=${encodeURIComponent(domain.id)}` },
|
|
48
|
+
{ key: 'purchases', label: 'Domain Purchases', icon: 'bi-receipt', route: 'purchases' },
|
|
49
|
+
{ key: 'registrant', label: 'Registrant Contact', icon: 'bi-person-vcard', route: 'registrant' },
|
|
50
|
+
{ key: 'credentials', label: 'Provider Credentials', icon: 'bi-key', route: 'credentials' },
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export const DNS_ADMIN_SECTION = {
|
|
54
|
+
id: 'dns',
|
|
55
|
+
title: 'DNS',
|
|
56
|
+
icon: 'bi-globe2',
|
|
57
|
+
navigationGroup: 'infrastructure' as const,
|
|
58
|
+
basePath: 'dns',
|
|
59
|
+
permissions: DNS_VIEW_PERMISSIONS,
|
|
60
|
+
routes: [
|
|
61
|
+
{
|
|
62
|
+
path: 'domains', label: 'Domains',
|
|
63
|
+
component: DomainsPage, permissions: DNS_VIEW_PERMISSIONS,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
path: 'records', label: 'DNS Records',
|
|
67
|
+
component: DnsRecordsPage, permissions: DNS_VIEW_PERMISSIONS,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
path: 'certificates', label: 'Certificates',
|
|
71
|
+
component: CertificatesPage, permissions: DNS_VIEW_PERMISSIONS,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: 'purchases', label: 'Domain Purchases',
|
|
75
|
+
component: DomainPurchasesPage, permissions: DNS_VIEW_PERMISSIONS,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
path: 'registrant', label: 'Registrant Contact',
|
|
79
|
+
component: RegistrantContactPage, permissions: DNS_MANAGE_PERMISSIONS,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
path: 'credentials', label: 'Provider Credentials',
|
|
83
|
+
component: ProviderCredentialsPage, permissions: DNS_VIEW_PERMISSIONS,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
} satisfies AdminSection;
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { defineModel, type Params } from '../../client';
|
|
2
|
+
import { sanitizeCertificateRow } from './certificate-data';
|
|
3
|
+
|
|
4
|
+
export { sanitizeCertificateRow } from './certificate-data';
|
|
5
|
+
|
|
6
|
+
export const DNS_VIEW_PERMISSIONS = ['sys.view_dns', 'sys.manage_dns', 'sys.security'];
|
|
7
|
+
export const DNS_MANAGE_PERMISSIONS = ['sys.manage_dns', 'sys.security'];
|
|
8
|
+
|
|
9
|
+
export interface DnsGroupChoice extends Record<string, unknown> { id: number; name: string }
|
|
10
|
+
export interface DnsGroupBasic extends DnsGroupChoice { is_active?: boolean; kind?: string }
|
|
11
|
+
|
|
12
|
+
export interface DnsUserBasic {
|
|
13
|
+
id: number;
|
|
14
|
+
display_name?: string;
|
|
15
|
+
username?: string;
|
|
16
|
+
last_login?: number | null;
|
|
17
|
+
last_activity?: number | null;
|
|
18
|
+
is_active?: boolean;
|
|
19
|
+
is_email_verified?: boolean;
|
|
20
|
+
is_phone_verified?: boolean;
|
|
21
|
+
is_dob_verified?: boolean;
|
|
22
|
+
avatar?: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DnsProviderCapability {
|
|
26
|
+
name: string;
|
|
27
|
+
purchase: boolean;
|
|
28
|
+
requires_credential: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DnsCapabilities {
|
|
32
|
+
purchase_enabled: boolean;
|
|
33
|
+
registrant_contact_configured: boolean;
|
|
34
|
+
max_domain_price: string;
|
|
35
|
+
currency: string;
|
|
36
|
+
quote_ttl_minutes: number;
|
|
37
|
+
allowed_record_types: string[];
|
|
38
|
+
search_batch_limit: number;
|
|
39
|
+
suggestions_enabled: boolean;
|
|
40
|
+
providers: DnsProviderCapability[];
|
|
41
|
+
acme: { configured: boolean; staging: boolean };
|
|
42
|
+
delegated_acme: {
|
|
43
|
+
available: boolean;
|
|
44
|
+
record_type: string;
|
|
45
|
+
target_suffix: string | null;
|
|
46
|
+
profile: string;
|
|
47
|
+
requires_provider_credentials: boolean;
|
|
48
|
+
};
|
|
49
|
+
cert_renew_days: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DnsCredentialRow {
|
|
53
|
+
id: number;
|
|
54
|
+
created: number;
|
|
55
|
+
modified: number;
|
|
56
|
+
name: string;
|
|
57
|
+
provider: string;
|
|
58
|
+
is_active: boolean;
|
|
59
|
+
verified: boolean;
|
|
60
|
+
verified_at: number | null;
|
|
61
|
+
domain_count: number;
|
|
62
|
+
last_error: string | null;
|
|
63
|
+
api_key_masked: string;
|
|
64
|
+
api_secret_masked: string;
|
|
65
|
+
group: DnsGroupBasic | number | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DomainRow {
|
|
69
|
+
id: number;
|
|
70
|
+
created: number;
|
|
71
|
+
modified?: number;
|
|
72
|
+
name: string;
|
|
73
|
+
provider: string;
|
|
74
|
+
status: string;
|
|
75
|
+
expires: number | null;
|
|
76
|
+
hosted_zone_id?: string | null;
|
|
77
|
+
auto_renew?: boolean;
|
|
78
|
+
privacy?: boolean;
|
|
79
|
+
verified?: boolean;
|
|
80
|
+
registered_on?: number | null;
|
|
81
|
+
last_error?: string | null;
|
|
82
|
+
group: DnsGroupBasic | number | null;
|
|
83
|
+
user?: DnsUserBasic | null;
|
|
84
|
+
credential?: Pick<DnsCredentialRow, 'id' | 'name' | 'provider' | 'is_active' | 'verified'> | number | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DomainPurchaseRow {
|
|
88
|
+
id: number;
|
|
89
|
+
created: number;
|
|
90
|
+
modified: number;
|
|
91
|
+
domain_name: string;
|
|
92
|
+
kind: string;
|
|
93
|
+
status: string;
|
|
94
|
+
price: string | number | null;
|
|
95
|
+
cost: string | number | null;
|
|
96
|
+
currency: string;
|
|
97
|
+
years: number;
|
|
98
|
+
quote_expires: number | null;
|
|
99
|
+
operation_id: string | null;
|
|
100
|
+
error: string | null;
|
|
101
|
+
group: DnsGroupBasic | number | null;
|
|
102
|
+
user: DnsUserBasic | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface CertificateRow {
|
|
106
|
+
id: number;
|
|
107
|
+
created: number;
|
|
108
|
+
modified: number;
|
|
109
|
+
common_name: string;
|
|
110
|
+
sans: string[];
|
|
111
|
+
status: string;
|
|
112
|
+
issuer: string | null;
|
|
113
|
+
serial: string | null;
|
|
114
|
+
not_before: number | null;
|
|
115
|
+
not_after: number | null;
|
|
116
|
+
renew_after: number | null;
|
|
117
|
+
last_error: string | null;
|
|
118
|
+
attempts: number;
|
|
119
|
+
days_remaining: number | null;
|
|
120
|
+
domain: Pick<DomainRow, 'id' | 'name' | 'provider' | 'status' | 'expires'> | number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface DnsRecordRow {
|
|
124
|
+
type: string;
|
|
125
|
+
name: string;
|
|
126
|
+
record_values: string[];
|
|
127
|
+
ttl: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface DnsRecordSetResponse {
|
|
131
|
+
domain: string;
|
|
132
|
+
provider: string;
|
|
133
|
+
records: DnsRecordRow[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface RegistrarSearchRow {
|
|
137
|
+
name: string;
|
|
138
|
+
available: boolean | null;
|
|
139
|
+
status: string | null;
|
|
140
|
+
price: string | number | null;
|
|
141
|
+
currency: string | null;
|
|
142
|
+
tld: string | null;
|
|
143
|
+
tld_supported: boolean | null;
|
|
144
|
+
privacy_supported: boolean | null;
|
|
145
|
+
reason: string | null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface RegistrarQuote {
|
|
149
|
+
purchase: number;
|
|
150
|
+
name: string;
|
|
151
|
+
price: string | number;
|
|
152
|
+
currency: string;
|
|
153
|
+
years: number;
|
|
154
|
+
/** One-use value. Keep in local call flow only; never Query-cache it. */
|
|
155
|
+
token: string;
|
|
156
|
+
expires: number;
|
|
157
|
+
privacy_supported: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface RegistrarPurchaseResult {
|
|
161
|
+
purchase: number;
|
|
162
|
+
domain: number;
|
|
163
|
+
name: string;
|
|
164
|
+
status: string;
|
|
165
|
+
operation_id: string | null;
|
|
166
|
+
privacy: boolean;
|
|
167
|
+
privacy_downgraded: boolean;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface RegistrarDiscoveryRow {
|
|
171
|
+
name: string;
|
|
172
|
+
registered: boolean;
|
|
173
|
+
hosted_zone: boolean;
|
|
174
|
+
hosted_zone_id: string | null;
|
|
175
|
+
record_count: number | null;
|
|
176
|
+
expires: number | null;
|
|
177
|
+
auto_renew: boolean | null;
|
|
178
|
+
tracked: boolean;
|
|
179
|
+
domain: number | null;
|
|
180
|
+
adoptable: boolean;
|
|
181
|
+
reason: string | null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface RegistrarDiscoveryResponse {
|
|
185
|
+
count: number;
|
|
186
|
+
truncated: boolean;
|
|
187
|
+
domains: RegistrarDiscoveryRow[];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface RegistrantExtraParam { Name: string; Value: string }
|
|
191
|
+
export interface RegistrantContact {
|
|
192
|
+
FirstName?: string; LastName?: string; ContactType?: string; OrganizationName?: string;
|
|
193
|
+
AddressLine1?: string; AddressLine2?: string; City?: string; State?: string;
|
|
194
|
+
CountryCode?: string; ZipCode?: string; PhoneNumber?: string; Email?: string;
|
|
195
|
+
Fax?: string; ExtraParams?: RegistrantExtraParam[];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface RegistrantContactResponse {
|
|
199
|
+
scope: 'group' | 'global';
|
|
200
|
+
group: number | null;
|
|
201
|
+
contact: RegistrantContact | null;
|
|
202
|
+
source: 'database' | 'settings_file' | 'none';
|
|
203
|
+
inherited: boolean;
|
|
204
|
+
effective_configured: boolean;
|
|
205
|
+
problems: string[];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface WhoisResponse {
|
|
209
|
+
name: string;
|
|
210
|
+
registrant: RegistrantContact;
|
|
211
|
+
admin: RegistrantContact;
|
|
212
|
+
tech: RegistrantContact;
|
|
213
|
+
privacy: boolean;
|
|
214
|
+
admin_privacy: boolean;
|
|
215
|
+
registrant_privacy: boolean;
|
|
216
|
+
tech_privacy: boolean;
|
|
217
|
+
auto_renew: boolean;
|
|
218
|
+
nameservers: string[];
|
|
219
|
+
registrar: string | null;
|
|
220
|
+
registered_on: number | null;
|
|
221
|
+
expires: number | null;
|
|
222
|
+
status_list: string[];
|
|
223
|
+
privacy_supported: boolean;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function rowObject(value: unknown): Record<string, unknown> {
|
|
227
|
+
return value != null && typeof value === 'object' && !Array.isArray(value)
|
|
228
|
+
? value as Record<string, unknown>
|
|
229
|
+
: {};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function projectGroup(value: unknown): DnsGroupBasic | number | null {
|
|
233
|
+
if (value == null || typeof value === 'number') return value as number | null;
|
|
234
|
+
const raw = rowObject(value);
|
|
235
|
+
return {
|
|
236
|
+
id: raw.id as number,
|
|
237
|
+
name: raw.name as string,
|
|
238
|
+
...(typeof raw.is_active === 'boolean' ? { is_active: raw.is_active } : {}),
|
|
239
|
+
...(typeof raw.kind === 'string' ? { kind: raw.kind } : {}),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function projectUser(value: unknown): DnsUserBasic | null {
|
|
244
|
+
if (value == null) return null;
|
|
245
|
+
const raw = rowObject(value);
|
|
246
|
+
return {
|
|
247
|
+
id: raw.id as number,
|
|
248
|
+
...(typeof raw.display_name === 'string' ? { display_name: raw.display_name } : {}),
|
|
249
|
+
...(typeof raw.username === 'string' ? { username: raw.username } : {}),
|
|
250
|
+
...(typeof raw.last_login === 'number' || raw.last_login === null ? { last_login: raw.last_login } : {}),
|
|
251
|
+
...(typeof raw.last_activity === 'number' || raw.last_activity === null ? { last_activity: raw.last_activity } : {}),
|
|
252
|
+
...(typeof raw.is_active === 'boolean' ? { is_active: raw.is_active } : {}),
|
|
253
|
+
...(typeof raw.is_email_verified === 'boolean' ? { is_email_verified: raw.is_email_verified } : {}),
|
|
254
|
+
...(typeof raw.is_phone_verified === 'boolean' ? { is_phone_verified: raw.is_phone_verified } : {}),
|
|
255
|
+
...(typeof raw.is_dob_verified === 'boolean' ? { is_dob_verified: raw.is_dob_verified } : {}),
|
|
256
|
+
...(typeof raw.avatar === 'string' || raw.avatar === null ? { avatar: raw.avatar } : {}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function projectCredentialRelation(value: unknown): DomainRow['credential'] {
|
|
261
|
+
if (value == null || typeof value === 'number') return value as number | null;
|
|
262
|
+
const raw = rowObject(value);
|
|
263
|
+
return {
|
|
264
|
+
id: raw.id as number,
|
|
265
|
+
name: raw.name as string,
|
|
266
|
+
provider: raw.provider as string,
|
|
267
|
+
is_active: raw.is_active as boolean,
|
|
268
|
+
verified: raw.verified as boolean,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Explicit safe graph projectors: unknown/equivalent secret containers drop by default. */
|
|
273
|
+
export function sanitizeDnsCredentialRow(row: unknown): DnsCredentialRow {
|
|
274
|
+
const raw = rowObject(row);
|
|
275
|
+
return {
|
|
276
|
+
id: raw.id as number, created: raw.created as number, modified: raw.modified as number,
|
|
277
|
+
name: raw.name as string, provider: raw.provider as string,
|
|
278
|
+
is_active: raw.is_active as boolean, verified: raw.verified as boolean,
|
|
279
|
+
verified_at: raw.verified_at as number | null, domain_count: raw.domain_count as number,
|
|
280
|
+
last_error: raw.last_error as string | null,
|
|
281
|
+
api_key_masked: raw.api_key_masked as string,
|
|
282
|
+
api_secret_masked: raw.api_secret_masked as string,
|
|
283
|
+
group: projectGroup(raw.group),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function sanitizeDomainRow(row: unknown): DomainRow {
|
|
288
|
+
const raw = rowObject(row);
|
|
289
|
+
return {
|
|
290
|
+
id: raw.id as number, created: raw.created as number,
|
|
291
|
+
...(typeof raw.modified === 'number' ? { modified: raw.modified } : {}),
|
|
292
|
+
name: raw.name as string, provider: raw.provider as string, status: raw.status as string,
|
|
293
|
+
expires: raw.expires as number | null, group: projectGroup(raw.group),
|
|
294
|
+
...(typeof raw.hosted_zone_id === 'string' || raw.hosted_zone_id === null ? { hosted_zone_id: raw.hosted_zone_id } : {}),
|
|
295
|
+
...(typeof raw.auto_renew === 'boolean' ? { auto_renew: raw.auto_renew } : {}),
|
|
296
|
+
...(typeof raw.privacy === 'boolean' ? { privacy: raw.privacy } : {}),
|
|
297
|
+
...(typeof raw.verified === 'boolean' ? { verified: raw.verified } : {}),
|
|
298
|
+
...(typeof raw.registered_on === 'number' || raw.registered_on === null ? { registered_on: raw.registered_on } : {}),
|
|
299
|
+
...(typeof raw.last_error === 'string' || raw.last_error === null ? { last_error: raw.last_error } : {}),
|
|
300
|
+
...('user' in raw ? { user: projectUser(raw.user) } : {}),
|
|
301
|
+
...('credential' in raw ? { credential: projectCredentialRelation(raw.credential) } : {}),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function sanitizeDomainPurchaseRow(row: unknown): DomainPurchaseRow {
|
|
306
|
+
const raw = rowObject(row);
|
|
307
|
+
return {
|
|
308
|
+
id: raw.id as number, created: raw.created as number, modified: raw.modified as number,
|
|
309
|
+
domain_name: raw.domain_name as string, kind: raw.kind as string, status: raw.status as string,
|
|
310
|
+
price: raw.price as string | number | null, cost: raw.cost as string | number | null,
|
|
311
|
+
currency: raw.currency as string, years: raw.years as number,
|
|
312
|
+
quote_expires: raw.quote_expires as number | null,
|
|
313
|
+
operation_id: raw.operation_id as string | null, error: raw.error as string | null,
|
|
314
|
+
group: projectGroup(raw.group), user: projectUser(raw.user),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function sanitizeRegistrarDiscoveryResponse(value: unknown): RegistrarDiscoveryResponse {
|
|
319
|
+
const raw = rowObject(value);
|
|
320
|
+
const domains = Array.isArray(raw.domains) ? raw.domains.map((value): RegistrarDiscoveryRow => {
|
|
321
|
+
const row = rowObject(value);
|
|
322
|
+
return {
|
|
323
|
+
name: row.name as string, registered: row.registered as boolean,
|
|
324
|
+
hosted_zone: row.hosted_zone as boolean, hosted_zone_id: row.hosted_zone_id as string | null,
|
|
325
|
+
record_count: row.record_count as number | null, expires: row.expires as number | null,
|
|
326
|
+
auto_renew: row.auto_renew as boolean | null, tracked: row.tracked as boolean,
|
|
327
|
+
domain: row.domain as number | null, adoptable: row.adoptable as boolean,
|
|
328
|
+
reason: row.reason as string | null,
|
|
329
|
+
};
|
|
330
|
+
}) : [];
|
|
331
|
+
return { count: raw.count as number, truncated: raw.truncated as boolean, domains };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const COMMON = new Set(['start', 'size', 'search']);
|
|
335
|
+
|
|
336
|
+
function normalizeListParams(params: Params, filters: ReadonlySet<string>, sorts: ReadonlySet<string>, graph: string): Params {
|
|
337
|
+
const safe: Params = { graph };
|
|
338
|
+
for (const [key, value] of Object.entries(params)) {
|
|
339
|
+
if (value == null || value === '') continue;
|
|
340
|
+
if (COMMON.has(key) || filters.has(key)) safe[key] = value;
|
|
341
|
+
}
|
|
342
|
+
if (typeof params.sort === 'string' && sorts.has(params.sort.replace(/^-/, ''))) safe.sort = params.sort;
|
|
343
|
+
return safe;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function normalizeCredentialListParams(params: Params): Params {
|
|
347
|
+
return normalizeListParams(
|
|
348
|
+
params,
|
|
349
|
+
new Set(['group', 'provider', 'provider__in', 'verified', 'is_active']),
|
|
350
|
+
new Set(['name', 'provider', 'verified', 'is_active', 'domain_count', 'created', 'modified']),
|
|
351
|
+
'default',
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function normalizeDomainListParams(params: Params): Params {
|
|
356
|
+
return normalizeListParams(
|
|
357
|
+
params,
|
|
358
|
+
new Set(['group', 'provider', 'status']),
|
|
359
|
+
new Set(['name', 'provider', 'status', 'expires', 'created']),
|
|
360
|
+
'list',
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function normalizePurchaseListParams(params: Params): Params {
|
|
365
|
+
if (params.graph != null && params.graph !== '' && params.graph !== 'basic') throw new Error('Purchase lists support only graph=basic');
|
|
366
|
+
for (const key of ['download_format', 'filename', 'export', 'download']) {
|
|
367
|
+
if (params[key] != null && params[key] !== '') throw new Error('Purchase export and download are not available');
|
|
368
|
+
}
|
|
369
|
+
return normalizeListParams(
|
|
370
|
+
params,
|
|
371
|
+
new Set(['group', 'kind', 'status', 'status__in', 'created__gte', 'created__lte']),
|
|
372
|
+
new Set(['domain_name', 'kind', 'status', 'price', 'created']),
|
|
373
|
+
'basic',
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function normalizeCertificateListParams(params: Params): Params {
|
|
378
|
+
if (params.graph != null && params.graph !== '' && params.graph !== 'default') {
|
|
379
|
+
throw new Error('Certificate lists support only graph=default');
|
|
380
|
+
}
|
|
381
|
+
for (const key of ['download_format', 'filename', 'export', 'download']) {
|
|
382
|
+
if (params[key] != null && params[key] !== '') throw new Error('Certificate export and download are not available');
|
|
383
|
+
}
|
|
384
|
+
return normalizeListParams(
|
|
385
|
+
params,
|
|
386
|
+
new Set(['domain', 'domain__exact', 'domain__group', 'status', 'status__in', 'not_after__gte', 'not_after__lte']),
|
|
387
|
+
new Set(['common_name', 'status', 'not_after', 'renew_after', 'created']),
|
|
388
|
+
'default',
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export const DomainModel = defineModel<DomainRow>({
|
|
393
|
+
name: 'dns_domain', endpoint: '/api/dnsman/domain',
|
|
394
|
+
permissions: { view: DNS_VIEW_PERMISSIONS, manage: DNS_MANAGE_PERMISSIONS },
|
|
395
|
+
normalizeListParams: normalizeDomainListParams,
|
|
396
|
+
sanitizeRow: sanitizeDomainRow,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
export const DnsCredentialModel = defineModel<DnsCredentialRow>({
|
|
400
|
+
name: 'dns_credential', endpoint: '/api/dnsman/credential',
|
|
401
|
+
permissions: { view: DNS_VIEW_PERMISSIONS, manage: DNS_MANAGE_PERMISSIONS, delete: DNS_MANAGE_PERMISSIONS },
|
|
402
|
+
normalizeListParams: normalizeCredentialListParams,
|
|
403
|
+
sanitizeRow: sanitizeDnsCredentialRow,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
export const DomainPurchaseModel = defineModel<DomainPurchaseRow>({
|
|
407
|
+
name: 'domain_purchase', endpoint: '/api/dnsman/purchase',
|
|
408
|
+
permissions: { view: DNS_VIEW_PERMISSIONS },
|
|
409
|
+
normalizeListParams: normalizePurchaseListParams,
|
|
410
|
+
sanitizeRow: sanitizeDomainPurchaseRow,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
export const CertificateModel = defineModel<CertificateRow>({
|
|
414
|
+
name: 'dns_certificate', endpoint: '/api/dnsman/certificate',
|
|
415
|
+
permissions: { view: DNS_VIEW_PERMISSIONS, manage: DNS_MANAGE_PERMISSIONS },
|
|
416
|
+
normalizeListParams: normalizeCertificateListParams,
|
|
417
|
+
sanitizeRow: sanitizeCertificateRow,
|
|
418
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { DomainPurchaseRow, RegistrarPurchaseResult, RegistrarQuote, RegistrarSearchRow } from './models';
|
|
2
|
+
|
|
3
|
+
const DOMAIN_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
4
|
+
export const PURCHASE_MAY_HAVE_MOVED = 'Money may have moved. Do not retry or request another quote. Check the AWS Route 53 Domains operations console and this purchase ledger first.';
|
|
5
|
+
|
|
6
|
+
export function normalizePurchaseDomain(value: unknown): string {
|
|
7
|
+
const domain = String(value ?? '').trim().toLowerCase().replace(/\.+$/, '');
|
|
8
|
+
if (!DOMAIN_RE.test(domain)) throw new Error('Enter a complete domain name, including its TLD');
|
|
9
|
+
return domain;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** TLDs stay caller-authored; the backend is the only supported-TLD authority. */
|
|
13
|
+
export function normalizeTypedTlds(value: string, limit: number): string[] {
|
|
14
|
+
const tlds = [...new Set(value.split(/[\s,]+/).map((item) => item.trim().toLowerCase().replace(/^\.+|\.+$/g, '')).filter(Boolean))];
|
|
15
|
+
if (!tlds.length) throw new Error('Enter at least one TLD');
|
|
16
|
+
if (tlds.length > limit) throw new Error(`Search is limited to ${limit} TLDs`);
|
|
17
|
+
if (tlds.some((tld) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(tld))) throw new Error('Enter valid TLD labels');
|
|
18
|
+
return tlds;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function searchAvailabilityLabel(row: RegistrarSearchRow): string {
|
|
22
|
+
return row.available === true ? 'Available' : row.available === false ? 'Unavailable' : 'Registry did not answer';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function decimalTupleEqual(left: string | number, right: string | number): boolean {
|
|
26
|
+
const normalize = (value: string | number) => {
|
|
27
|
+
const match = String(value).trim().match(/^(\d+)(?:\.(\d{1,2}))?$/);
|
|
28
|
+
if (!match) return null;
|
|
29
|
+
return BigInt(match[1]!) * 100n + BigInt((match[2] ?? '').padEnd(2, '0'));
|
|
30
|
+
};
|
|
31
|
+
const a = normalize(left); const b = normalize(right);
|
|
32
|
+
return a != null && b != null && a === b;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface QuoteIdentity { purchase: number; group: number; domain: string; years: number; price: string; currency: string; expires: number }
|
|
36
|
+
export function quoteIdentity(quote: RegistrarQuote, group: number): QuoteIdentity {
|
|
37
|
+
return { purchase: quote.purchase, group, domain: normalizePurchaseDomain(quote.name), years: quote.years, price: String(quote.price), currency: quote.currency, expires: quote.expires };
|
|
38
|
+
}
|
|
39
|
+
export function quoteMatches(identity: QuoteIdentity, input: { group: number; domain: string; years: number; price: string | number; currency: string }, now = Date.now() / 1000): boolean {
|
|
40
|
+
return identity.group === input.group && identity.domain === normalizePurchaseDomain(input.domain) && identity.years === input.years
|
|
41
|
+
&& identity.currency === input.currency && decimalTupleEqual(identity.price, input.price) && identity.expires > now;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function redactRegistrarError(error: unknown): string {
|
|
45
|
+
const text = error instanceof Error ? error.message : String(error ?? 'Registrar operation failed');
|
|
46
|
+
return text.slice(0, 600).replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, '$1 [redacted]')
|
|
47
|
+
.replace(/([?&;](?:token|confirm_token|api_key|secret|password)=)[^&#;\s]*/gi, '$1[redacted]')
|
|
48
|
+
.replace(/\b(confirm[_ -]?token|secret|password)(\s*(?:is|:|=)?\s+)[A-Za-z0-9._~+/=-]{6,}/gi, '$1$2[redacted]');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function sanitizePurchaseResult(value: unknown): RegistrarPurchaseResult {
|
|
52
|
+
const row = value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
53
|
+
if (!Number.isInteger(row.purchase) || !Number.isInteger(row.domain) || typeof row.name !== 'string' || typeof row.status !== 'string') throw new Error('Registrar returned a malformed purchase acknowledgement');
|
|
54
|
+
return { purchase: Number(row.purchase), domain: Number(row.domain), name: normalizePurchaseDomain(row.name), status: row.status.slice(0, 32), operation_id: typeof row.operation_id === 'string' ? row.operation_id.slice(0, 128) : null, privacy: row.privacy === true, privacy_downgraded: row.privacy_downgraded === true };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function purchaseTerminal(row: Pick<DomainPurchaseRow, 'status'>): boolean { return ['completed', 'failed', 'expired'].includes(row.status); }
|
|
58
|
+
export const PURCHASE_POLL_DELAYS_MS = [1_000, 2_000, 3_000, 5_000, 8_000, 13_000] as const;
|
|
59
|
+
export async function pollPurchaseLedger(options: { purchase: number; fetch: (id: number) => Promise<DomainPurchaseRow>; isCurrent?: () => boolean; delays?: readonly number[] }): Promise<DomainPurchaseRow | null> {
|
|
60
|
+
for (const delay of [0, ...(options.delays ?? PURCHASE_POLL_DELAYS_MS)]) {
|
|
61
|
+
if (delay) await new Promise((resolve) => setTimeout(resolve, delay));
|
|
62
|
+
if (options.isCurrent?.() === false) return null;
|
|
63
|
+
const row = await options.fetch(options.purchase);
|
|
64
|
+
if (purchaseTerminal(row)) return row;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { RegistrantContact, RegistrantContactResponse } from './models';
|
|
2
|
+
|
|
3
|
+
export const CONTACT_STRING_FIELDS = ['FirstName', 'LastName', 'ContactType', 'OrganizationName', 'AddressLine1', 'AddressLine2', 'City', 'State', 'CountryCode', 'ZipCode', 'PhoneNumber', 'Email', 'Fax'] as const;
|
|
4
|
+
const CONTACT_TYPES = new Set(['PERSON', 'COMPANY', 'ASSOCIATION', 'PUBLIC_BODY', 'RESELLER']);
|
|
5
|
+
|
|
6
|
+
export function sanitizeRegistrantResponse(value: unknown): RegistrantContactResponse {
|
|
7
|
+
const raw = value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
8
|
+
if (!['group', 'global'].includes(String(raw.scope)) || !(raw.group == null || Number.isInteger(raw.group)) || !['database', 'settings_file', 'none'].includes(String(raw.source)) || typeof raw.inherited !== 'boolean' || typeof raw.effective_configured !== 'boolean' || !Array.isArray(raw.problems)) throw new Error('Registrant contact response is malformed');
|
|
9
|
+
let contact: RegistrantContact | null = null;
|
|
10
|
+
if (raw.contact != null) {
|
|
11
|
+
if (typeof raw.contact !== 'object' || Array.isArray(raw.contact)) throw new Error('Registrant contact response is malformed');
|
|
12
|
+
const source = raw.contact as Record<string, unknown>; contact = {};
|
|
13
|
+
for (const key of CONTACT_STRING_FIELDS) if (typeof source[key] === 'string') contact[key] = source[key].slice(0, 500);
|
|
14
|
+
if (Array.isArray(source.ExtraParams)) contact.ExtraParams = source.ExtraParams.slice(0, 25).flatMap((item) => {
|
|
15
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return [];
|
|
16
|
+
const entry = item as Record<string, unknown>;
|
|
17
|
+
return typeof entry.Name === 'string' && typeof entry.Value === 'string' ? [{ Name: entry.Name.slice(0, 100), Value: entry.Value.slice(0, 500) }] : [];
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return { scope: raw.scope as 'group' | 'global', group: raw.group as number | null, contact, source: String(raw.source) as RegistrantContactResponse['source'], inherited: raw.inherited, effective_configured: raw.effective_configured, problems: raw.problems.filter((item): item is string => typeof item === 'string').slice(0, 20).map((item) => item.slice(0, 300)) };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function contactDraft(response: RegistrantContactResponse): RegistrantContact {
|
|
24
|
+
if (!response.contact || response.inherited) return {};
|
|
25
|
+
const draft: RegistrantContact = {};
|
|
26
|
+
for (const key of CONTACT_STRING_FIELDS) {
|
|
27
|
+
if (key === 'Fax' && response.source !== 'database') continue;
|
|
28
|
+
if (typeof response.contact[key] === 'string') draft[key] = response.contact[key];
|
|
29
|
+
}
|
|
30
|
+
if (response.source === 'database' && response.contact.ExtraParams) draft.ExtraParams = response.contact.ExtraParams.map((entry) => ({ ...entry }));
|
|
31
|
+
return draft;
|
|
32
|
+
}
|
|
33
|
+
export function contactPayload(draft: RegistrantContact, original: RegistrantContactResponse, scope: { group: number | null }): RegistrantContact {
|
|
34
|
+
const payload: RegistrantContact = {};
|
|
35
|
+
const sameScopeDatabase = original.source === 'database' && original.scope === (scope.group == null ? 'global' : 'group') && original.group === scope.group;
|
|
36
|
+
for (const key of CONTACT_STRING_FIELDS) {
|
|
37
|
+
if (key === 'Fax' && !sameScopeDatabase) continue;
|
|
38
|
+
const value = draft[key]; if (typeof value === 'string' && value.trim()) payload[key] = key === 'CountryCode' || key === 'ContactType' ? value.trim().toUpperCase() : value.trim();
|
|
39
|
+
}
|
|
40
|
+
if (sameScopeDatabase && original.contact?.ExtraParams) payload.ExtraParams = original.contact.ExtraParams.map((entry) => ({ ...entry }));
|
|
41
|
+
return payload;
|
|
42
|
+
}
|
|
43
|
+
export function validateContactDraft(contact: RegistrantContact): string[] {
|
|
44
|
+
const required = ['FirstName', 'LastName', 'ContactType', 'AddressLine1', 'City', 'CountryCode', 'ZipCode', 'PhoneNumber', 'Email'] as const;
|
|
45
|
+
const problems = required.filter((key) => !String(contact[key] ?? '').trim()).map((key) => `${key} is required`);
|
|
46
|
+
if (contact.ContactType && !CONTACT_TYPES.has(contact.ContactType.toUpperCase())) problems.push('ContactType is invalid');
|
|
47
|
+
if (contact.CountryCode && !/^[A-Za-z]{2}$/.test(contact.CountryCode)) problems.push('CountryCode must be two letters');
|
|
48
|
+
if (contact.PhoneNumber && !/^\+\d{1,3}\.\d{4,15}$/.test(contact.PhoneNumber)) problems.push('PhoneNumber must use +country.number format');
|
|
49
|
+
if (contact.Email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(contact.Email)) problems.push('Email is invalid');
|
|
50
|
+
if (['US', 'CA'].includes(String(contact.CountryCode ?? '').toUpperCase()) && !String(contact.State ?? '').trim()) problems.push('State is required for US and CA contacts');
|
|
51
|
+
return problems;
|
|
52
|
+
}
|