@rapidmx/web-client 0.4.0 → 0.6.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/README.md +88 -0
- package/apps/admin/audit-log/index.tsx +35 -8
- package/apps/admin/data-requests/index.tsx +480 -485
- package/apps/admin/distribution-lists/[uid].tsx +19 -1
- package/apps/admin/escrow-scopes/[uid].tsx +348 -237
- package/apps/admin/escrow-scopes/new/index.tsx +8 -2
- package/apps/admin/index.tsx +19 -1
- package/apps/admin/ingest-queue/index.tsx +44 -5
- package/apps/admin/mailboxes/[uid].tsx +211 -184
- package/apps/admin/quarantine/index.tsx +46 -14
- package/apps/admin/transport-rules/[uid].tsx +30 -1
- package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
- package/apps/admin/transport-rules/index.tsx +24 -6
- package/apps/admin/transport-rules/new/index.tsx +30 -1
- package/apps/escrow/_layout.tsx +2 -3
- package/apps/escrow/audit-log/index.tsx +196 -168
- package/apps/escrow/matters/[uid].tsx +621 -521
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
- package/apps/shared/components/admin/layout/AdminShell.tsx +28 -9
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
- package/apps/shared/components/admin/settings/BrandingForm.tsx +15 -4
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +18 -7
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +10 -2
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +6 -0
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +45 -12
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -595
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -98
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +49 -13
- package/apps/shared/components/admin/setup/SetupWizard.tsx +150 -34
- package/apps/shared/components/admin/signOut.ts +79 -0
- package/apps/shared/components/admin/usePagedList.tsx +129 -0
- package/apps/shared/components/calendar/EventModal.tsx +717 -549
- package/apps/shared/components/calendar/MonthView.tsx +173 -139
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
- package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
- package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
- package/apps/shared/components/calendar/allDay.ts +124 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +194 -192
- package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
- package/apps/shared/components/contacts/ContactForm.tsx +377 -348
- package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
- package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
- package/apps/shared/components/contacts/contactKeys.ts +78 -0
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +199 -197
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
- package/apps/shared/components/layout/AppShell.tsx +123 -33
- package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +389 -298
- package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -128
- package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
- package/apps/shared/components/mail/MessageDetailPane.tsx +1318 -630
- package/apps/shared/components/mail/compose/ComposeContext.tsx +20 -15
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +854 -132
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +420 -408
- package/apps/shared/components/mail/pinnedSigners.ts +124 -0
- package/apps/shared/components/mail/verificationSeals.ts +125 -0
- package/apps/shared/components/mail/writableMailboxes.ts +101 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
- package/apps/shared/components/settings/layout/SettingsShell.tsx +23 -9
- package/apps/shared/components/tasks/layout/TasksShell.tsx +201 -199
- package/apps/shared/mail/listAllPages.ts +39 -0
- package/apps/shared/plugins/pluginNav.ts +73 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +64 -55
- package/apps/shared/search/localIndexBuilder.ts +481 -179
- package/apps/shared/search/localIndexRpcClient.ts +231 -29
- package/apps/shared/search/localIndexSchema.ts +15 -6
- package/apps/shared/search/localIndexStorage.ts +98 -0
- package/apps/shared/search/localIndexVFS.ts +32 -1
- package/apps/shared/search/localIndexWorker.ts +648 -186
- package/apps/www/calendar/index.tsx +438 -417
- package/apps/www/contacts/[uid].tsx +21 -2
- package/apps/www/contacts/index.tsx +116 -12
- package/apps/www/index.tsx +1314 -1041
- package/apps/www/messages/[uid].tsx +18 -6
- package/apps/www/settings/auto-reply/index.tsx +134 -129
- package/apps/www/settings/encryption/index.tsx +739 -216
- package/apps/www/settings/filters/[uid].tsx +171 -155
- package/apps/www/settings/filters/new/index.tsx +8 -1
- package/apps/www/settings/focused-inbox/index.tsx +150 -148
- package/apps/www/settings/privacy/index.tsx +494 -436
- package/apps/www/settings/read-receipts/index.tsx +148 -125
- package/apps/www/settings/sharing/index.tsx +17 -14
- package/apps/www/tasks/index.tsx +638 -579
- package/dist/apps/admin/_404.d.ts +2 -0
- package/dist/apps/admin/_500.d.ts +2 -0
- package/dist/apps/admin/_layout.d.ts +8 -0
- package/dist/apps/admin/audit-log/index.d.ts +11 -0
- package/dist/apps/admin/audit-log/index.js +38 -8
- package/dist/apps/admin/branding/index.d.ts +3 -0
- package/dist/apps/admin/data-requests/index.d.ts +3 -0
- package/dist/apps/admin/data-requests/index.js +52 -72
- package/dist/apps/admin/distribution-lists/[uid].d.ts +7 -0
- package/dist/apps/admin/distribution-lists/[uid].js +1 -1
- package/dist/apps/admin/distribution-lists/index.d.ts +3 -0
- package/dist/apps/admin/distribution-lists/new/index.d.ts +3 -0
- package/dist/apps/admin/domains/[uid].d.ts +7 -0
- package/dist/apps/admin/domains/index.d.ts +3 -0
- package/dist/apps/admin/domains/new/index.d.ts +3 -0
- package/dist/apps/admin/encryption-policy/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/[uid].d.ts +21 -0
- package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
- package/dist/apps/admin/escrow-scopes/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/new/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
- package/dist/apps/admin/index.d.ts +3 -0
- package/dist/apps/admin/index.js +4 -1
- package/dist/apps/admin/ingest-queue/index.d.ts +4 -0
- package/dist/apps/admin/ingest-queue/index.js +24 -6
- package/dist/apps/admin/mailbox-policy/index.d.ts +3 -0
- package/dist/apps/admin/mailboxes/[uid].d.ts +7 -0
- package/dist/apps/admin/mailboxes/[uid].js +20 -4
- package/dist/apps/admin/mailboxes/new/index.d.ts +3 -0
- package/dist/apps/admin/plugins/index.d.ts +3 -0
- package/dist/apps/admin/quarantine/index.d.ts +6 -0
- package/dist/apps/admin/quarantine/index.js +20 -11
- package/dist/apps/admin/retention-policy/index.d.ts +3 -0
- package/dist/apps/admin/setup/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/[uid].d.ts +7 -0
- package/dist/apps/admin/transport-rules/[uid].js +18 -2
- package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +21 -0
- package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
- package/dist/apps/admin/transport-rules/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/index.js +22 -6
- package/dist/apps/admin/transport-rules/new/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/new/index.js +18 -2
- package/dist/apps/escrow/_layout.d.ts +10 -0
- package/dist/apps/escrow/_layout.js +3 -3
- package/dist/apps/escrow/audit-log/index.d.ts +9 -0
- package/dist/apps/escrow/audit-log/index.js +26 -1
- package/dist/apps/escrow/index.d.ts +3 -0
- package/dist/apps/escrow/matters/[uid].d.ts +7 -0
- package/dist/apps/escrow/matters/[uid].js +77 -42
- package/dist/apps/escrow/matters/new/index.d.ts +3 -0
- package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +17 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +32 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +32 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.js +15 -6
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +12 -0
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
- package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +6 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +19 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +12 -0
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +15 -4
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +13 -7
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +7 -2
- package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +19 -0
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +6 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +10 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +34 -12
- package/dist/apps/shared/components/admin/settings/PluginsManager.d.ts +4 -0
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +390 -78
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +40 -11
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.d.ts +14 -0
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +29 -13
- package/dist/apps/shared/components/admin/setup/SetupWizard.d.ts +36 -0
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +87 -21
- package/dist/apps/shared/components/admin/signOut.d.ts +23 -0
- package/dist/apps/shared/components/admin/signOut.js +75 -0
- package/dist/apps/shared/components/admin/usePagedList.d.ts +39 -0
- package/dist/apps/shared/components/admin/usePagedList.js +99 -0
- package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +26 -0
- package/dist/apps/shared/components/calendar/EventModal.d.ts +46 -0
- package/dist/apps/shared/components/calendar/EventModal.js +142 -21
- package/dist/apps/shared/components/calendar/MonthView.d.ts +15 -0
- package/dist/apps/shared/components/calendar/MonthView.js +12 -5
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +20 -0
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
- package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +19 -0
- package/dist/apps/shared/components/calendar/SplitDayView.d.ts +28 -0
- package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
- package/dist/apps/shared/components/calendar/TimeGridView.d.ts +17 -0
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
- package/dist/apps/shared/components/calendar/allDay.d.ts +51 -0
- package/dist/apps/shared/components/calendar/allDay.js +111 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +40 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +23 -0
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
- package/dist/apps/shared/components/contacts/ContactForm.d.ts +20 -0
- package/dist/apps/shared/components/contacts/ContactForm.js +32 -10
- package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +40 -0
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +25 -0
- package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +40 -0
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
- package/dist/apps/shared/components/contacts/contactKeys.d.ts +29 -0
- package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
- package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +21 -0
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +2 -2
- package/dist/apps/shared/components/escrow/layout/EscrowShell.d.ts +25 -0
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
- package/dist/apps/shared/components/forms/StringListField.d.ts +22 -0
- package/dist/apps/shared/components/layout/AppShell.d.ts +49 -0
- package/dist/apps/shared/components/layout/AppShell.js +97 -26
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +18 -0
- package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +51 -0
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +49 -12
- package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +13 -0
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +66 -0
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +45 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +73 -14
- package/dist/apps/shared/components/layout/UserMenu.d.ts +23 -0
- package/dist/apps/shared/components/mail/ConversationList.d.ts +11 -0
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +23 -0
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +93 -0
- package/dist/apps/shared/components/mail/MessageDetailPane.js +441 -36
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +58 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +15 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +37 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +699 -128
- package/dist/apps/shared/components/mail/compose/EmojiPicker.d.ts +14 -0
- package/dist/apps/shared/components/mail/compose/GifPicker.d.ts +13 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +34 -0
- package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.d.ts +16 -0
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +27 -0
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +57 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +13 -4
- package/dist/apps/shared/components/mail/pinnedSigners.d.ts +33 -0
- package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
- package/dist/apps/shared/components/mail/verificationSeals.d.ts +22 -0
- package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
- package/dist/apps/shared/components/mail/writableMailboxes.d.ts +15 -0
- package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
- package/dist/apps/shared/components/rules/RuleBuilder.d.ts +70 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +41 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +19 -9
- package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +41 -0
- package/dist/apps/shared/components/tasks/TasksToolbar.d.ts +14 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +24 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +2 -2
- package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -0
- package/dist/apps/shared/mail/listAllPages.d.ts +17 -0
- package/dist/apps/shared/mail/listAllPages.js +25 -0
- package/dist/apps/shared/plugins/pluginNav.d.ts +39 -0
- package/dist/apps/shared/plugins/pluginNav.js +33 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +13 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +58 -54
- package/dist/apps/shared/search/localIndexBlockCipher.d.ts +36 -0
- package/dist/apps/shared/search/localIndexBuilder.d.ts +85 -0
- package/dist/apps/shared/search/localIndexBuilder.js +292 -37
- package/dist/apps/shared/search/localIndexKey.d.ts +4 -0
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +71 -0
- package/dist/apps/shared/search/localIndexRpcClient.js +191 -26
- package/dist/apps/shared/search/localIndexSchema.d.ts +111 -0
- package/dist/apps/shared/search/localIndexSchema.js +12 -6
- package/dist/apps/shared/search/localIndexSizePreference.d.ts +27 -0
- package/dist/apps/shared/search/localIndexStorage.d.ts +28 -0
- package/dist/apps/shared/search/localIndexStorage.js +86 -0
- package/dist/apps/shared/search/localIndexVFS.d.ts +101 -0
- package/dist/apps/shared/search/localIndexVFS.js +68 -40
- package/dist/apps/shared/search/localIndexWorker.d.ts +145 -0
- package/dist/apps/shared/search/localIndexWorker.js +519 -173
- package/dist/apps/shared/search/searchTier2.d.ts +30 -0
- package/dist/apps/www/_404.d.ts +2 -0
- package/dist/apps/www/_500.d.ts +2 -0
- package/dist/apps/www/_layout.d.ts +14 -0
- package/dist/apps/www/calendar/index.d.ts +3 -0
- package/dist/apps/www/calendar/index.js +28 -10
- package/dist/apps/www/contacts/[uid].d.ts +13 -0
- package/dist/apps/www/contacts/[uid].js +13 -2
- package/dist/apps/www/contacts/index.d.ts +3 -0
- package/dist/apps/www/contacts/index.js +80 -10
- package/dist/apps/www/index.d.ts +3 -0
- package/dist/apps/www/index.js +339 -108
- package/dist/apps/www/messages/[uid].d.ts +11 -0
- package/dist/apps/www/messages/[uid].js +18 -6
- package/dist/apps/www/settings/auto-reply/index.d.ts +4 -0
- package/dist/apps/www/settings/auto-reply/index.js +9 -4
- package/dist/apps/www/settings/encryption/index.d.ts +7 -0
- package/dist/apps/www/settings/encryption/index.js +514 -97
- package/dist/apps/www/settings/filters/[uid].d.ts +8 -0
- package/dist/apps/www/settings/filters/[uid].js +22 -7
- package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +11 -0
- package/dist/apps/www/settings/filters/index.d.ts +4 -0
- package/dist/apps/www/settings/filters/new/index.d.ts +4 -0
- package/dist/apps/www/settings/filters/new/index.js +8 -2
- package/dist/apps/www/settings/focused-inbox/index.d.ts +4 -0
- package/dist/apps/www/settings/focused-inbox/index.js +3 -1
- package/dist/apps/www/settings/labels/index.d.ts +4 -0
- package/dist/apps/www/settings/privacy/index.d.ts +4 -0
- package/dist/apps/www/settings/privacy/index.js +57 -22
- package/dist/apps/www/settings/read-receipts/index.d.ts +4 -0
- package/dist/apps/www/settings/read-receipts/index.js +10 -3
- package/dist/apps/www/settings/sharing/index.d.ts +4 -0
- package/dist/apps/www/settings/sharing/index.js +7 -11
- package/dist/apps/www/settings/signatures/[uid].d.ts +8 -0
- package/dist/apps/www/settings/signatures/index.d.ts +4 -0
- package/dist/apps/www/settings/signatures/new/index.d.ts +4 -0
- package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +10 -0
- package/dist/apps/www/tasks/index.d.ts +3 -0
- package/dist/apps/www/tasks/index.js +50 -10
- package/package.json +3 -3
- package/apps/shared/components/booking/AvailabilityEditor.tsx +0 -118
- package/apps/www/settings/booking-types/[uid].tsx +0 -300
- package/apps/www/settings/booking-types/index.tsx +0 -98
- package/apps/www/settings/booking-types/new/index.tsx +0 -212
- package/dist/apps/shared/components/booking/AvailabilityEditor.js +0 -45
- package/dist/apps/www/settings/booking-types/[uid].js +0 -129
- package/dist/apps/www/settings/booking-types/index.js +0 -29
- package/dist/apps/www/settings/booking-types/new/index.js +0 -86
|
@@ -14,7 +14,32 @@ const ACTION_LABELS = {
|
|
|
14
14
|
"escrow_access_request.created": "Access request created",
|
|
15
15
|
"escrow_access_request.approved": "Access request approved",
|
|
16
16
|
"escrow_access_request.material_read": "Material read",
|
|
17
|
+
"matter_export.requested": "Matter export requested",
|
|
18
|
+
"matter_export.ready": "Matter export ready",
|
|
19
|
+
"matter_export.failed": "Matter export failed",
|
|
17
20
|
};
|
|
21
|
+
/** The label for an audit action - the raw action name for one this console doesn't know yet (a newer server). */
|
|
22
|
+
export function actionLabel(action) {
|
|
23
|
+
return ACTION_LABELS[action] ?? action;
|
|
24
|
+
}
|
|
25
|
+
const VERIFICATION_FAILURES = {
|
|
26
|
+
link_mismatch: "An entry was deleted, inserted or reordered.",
|
|
27
|
+
hash_mismatch: "An entry was edited after it was written.",
|
|
28
|
+
unknown_algorithm: "An entry uses a hash algorithm this server doesn't recognize.",
|
|
29
|
+
algorithm_downgrade: "An unkeyed entry follows keyed ones, which suggests a forged entry.",
|
|
30
|
+
hmac_key_unavailable: "Audit key not configured: this server can't check keyed entries, so the chain couldn't be verified.",
|
|
31
|
+
truncated: "Entries missing at the end: entries were deleted from the end of the chain.",
|
|
32
|
+
head_mismatch: "The chain's head record doesn't match its last entry.",
|
|
33
|
+
head_mac_mismatch: "The chain's head record was forged or edited.",
|
|
34
|
+
head_missing: "The chain's head record is missing.",
|
|
35
|
+
};
|
|
36
|
+
/** Explains a failed chain verification by its `reason` (plus where it broke, when known). An unrecognized or missing
|
|
37
|
+
* reason (a newer or older server) reads as a generic failure. */
|
|
38
|
+
export function describeVerificationFailure(result) {
|
|
39
|
+
const where = result.brokenAtSequence === undefined ? "" : ` (at sequence ${result.brokenAtSequence})`;
|
|
40
|
+
const known = result.reason === undefined ? undefined : VERIFICATION_FAILURES[result.reason];
|
|
41
|
+
return known ? `Chain integrity check failed${where}. ${known}` : `Chain integrity check failed${where}.`;
|
|
42
|
+
}
|
|
18
43
|
export default function EscrowAuditLogPage(props) {
|
|
19
44
|
return (_jsx(EscrowShell, { ...props, active: "auditLog", children: _jsx(EscrowAuditLogContent, {}) }));
|
|
20
45
|
}
|
|
@@ -62,5 +87,5 @@ function EscrowAuditLogContent() {
|
|
|
62
87
|
}
|
|
63
88
|
const hasNextPage = entries.length === PAGE_SIZE;
|
|
64
89
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between mb-5", children: [_jsx("h1", { className: "text-xl font-bold uppercase tracking-wide", children: "Escrow audit log" }), isTrustedAdmin && (_jsx(Button, { type: "button", className: "!w-auto", loading: verifying, disabled: verifying, onClick: handleVerify, children: "Verify chain integrity" }))] }), _jsx("p", { className: "text-sm text-text-muted mb-5", children: "Every escrow access lifecycle event visible to you \u2014 matters under a scope you hold. A trusted administrator sees every entry across every scope instead." }), verifyError && _jsx(Alert, { children: verifyError }), verifyResult &&
|
|
65
|
-
(verifyResult.valid ? (_jsx("div", { className: "mb-5 py-3 px-3.5 rounded-sm text-sm bg-success/10 text-success font-medium", children: "Chain verified \u2014 no tampering detected." })) : (
|
|
90
|
+
(verifyResult.valid ? (_jsx("div", { className: "mb-5 py-3 px-3.5 rounded-sm text-sm bg-success/10 text-success font-medium", children: "Chain verified \u2014 no tampering detected." })) : (_jsx(Alert, { children: describeVerificationFailure(verifyResult) }))), error && _jsx(Alert, { children: error }), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : entries.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No audit log entries visible to you yet." })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm border-collapse", children: [_jsx("thead", { children: _jsx("tr", { children: ["Sequence", "Action", "Holder", "Mailbox", "Occurred"].map((h) => (_jsx("th", { className: "text-left text-xs uppercase tracking-wide text-text-muted py-2 px-2.5 border-b border-border", children: h }, h))) }) }), _jsx("tbody", { children: entries.map((entry) => (_jsxs("tr", { children: [_jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: entry.sequence }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: actionLabel(entry.action) }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: entry.holderUserUid }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: entry.mailboxUid }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: new Date(entry.occurredAt).toLocaleString() })] }, entry.uid))) })] }) })), _jsxs("div", { className: "flex gap-3 items-center mt-4", children: [_jsx(Button, { variant: "secondary", type: "button", className: "!w-auto", disabled: page === 0 || loading, onClick: () => setPage((p) => Math.max(0, p - 1)), children: "Previous" }), _jsxs("span", { className: "text-sm text-text-muted", children: ["Page ", page + 1] }), _jsx(Button, { variant: "secondary", type: "button", className: "!w-auto", disabled: !hasNextPage || loading, onClick: () => setPage((p) => p + 1), children: "Next" })] })] }));
|
|
66
91
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
-
import { useEffect, useState } from "react";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
8
|
import { closeMatter, getMatter } from "@rapidmx/react-shared/admin/mattersApi.js";
|
|
9
9
|
import { approveAccessRequest, createAccessRequest, denyAccessRequest, getAccessRequestMaterial, listAccessRequests, } from "@rapidmx/react-shared/admin/escrowAccessRequestsApi.js";
|
|
@@ -11,35 +11,44 @@ import { createMatterExportRequest, listMatterExportRequests, matterExportReques
|
|
|
11
11
|
import { searchMatter } from "@rapidmx/react-shared/admin/matterSearchApi.js";
|
|
12
12
|
import { parseSearchQuery } from "@rapidmx/react-shared/search/queryGrammar.js";
|
|
13
13
|
import EscrowShell from "../../shared/components/escrow/layout/EscrowShell.js";
|
|
14
|
+
import { LoadMoreButton, usePagedList } from "../../shared/components/admin/usePagedList.js";
|
|
14
15
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
15
16
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
16
17
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
17
18
|
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
18
19
|
const INPUT_CLASS = "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// matter's requests. This page fetches a generously-sized single page and filters client-side instead of
|
|
23
|
-
// paginating a second, matter-scoped list UI on top of an already-scoped one; a holder with more than this
|
|
24
|
-
// many *total* in-flight requests across every matter they hold is not the common case this v1 targets.
|
|
25
|
-
const REQUESTS_FETCH_LIMIT = 200;
|
|
20
|
+
/** Request statuses still waiting or running - shown as a neutral in-progress badge, never as success. Includes
|
|
21
|
+
* restapi's `processing` (exports/imports) and `in_progress` (an erasure that is running). */
|
|
22
|
+
const IN_PROGRESS_STATUSES = new Set(["pending", "processing", "in_progress"]);
|
|
26
23
|
function statusBadgeClass(status) {
|
|
27
|
-
if (status === "
|
|
24
|
+
if (status === "failed" || status === "denied")
|
|
28
25
|
return "bg-danger-bg text-danger";
|
|
29
|
-
if (status
|
|
26
|
+
if (IN_PROGRESS_STATUSES.has(status))
|
|
30
27
|
return "bg-surface-alt text-text-muted";
|
|
31
28
|
return "bg-success text-white";
|
|
32
29
|
}
|
|
30
|
+
/** A status as shown on its badge (`in_progress` -> `in progress`). */
|
|
31
|
+
function statusLabel(status) {
|
|
32
|
+
return status.replace(/_/g, " ");
|
|
33
|
+
}
|
|
33
34
|
export default function MatterDetailPage(props) {
|
|
34
35
|
return (_jsx(EscrowShell, { ...props, active: "matters", children: _jsx(MatterDetailContent, { uid: props.params.uid }) }));
|
|
35
36
|
}
|
|
37
|
+
function formatDateRange(matter) {
|
|
38
|
+
return `${new Date(matter.dateRangeStart).toLocaleDateString()} – ${new Date(matter.dateRangeEnd).toLocaleDateString()}`;
|
|
39
|
+
}
|
|
36
40
|
function MatterDetailContent({ uid }) {
|
|
37
41
|
const [matter, setMatter] = useState(null);
|
|
38
|
-
const [requests, setRequests] = useState([]);
|
|
39
42
|
const [loading, setLoading] = useState(true);
|
|
40
43
|
const [error, setError] = useState(null);
|
|
44
|
+
// Both lists are fetched for this matter only (`matterId`), newest first, a page at a time. Rows are still
|
|
45
|
+
// checked against this matter in case an older server ignores the filter.
|
|
46
|
+
const requests = usePagedList((params) => listAccessRequests({ ...params, matterId: uid }), "Could not load this matter's access requests.");
|
|
47
|
+
const exportRequests = usePagedList((params) => listMatterExportRequests({ ...params, matterId: uid }), "Could not load this matter's export requests.");
|
|
48
|
+
const [confirmingClose, setConfirmingClose] = useState(false);
|
|
41
49
|
const [closing, setClosing] = useState(false);
|
|
42
50
|
const [closeError, setCloseError] = useState(null);
|
|
51
|
+
const [approveTarget, setApproveTarget] = useState(null);
|
|
43
52
|
const [showNewRequest, setShowNewRequest] = useState(false);
|
|
44
53
|
const [newRequestMailboxUid, setNewRequestMailboxUid] = useState("");
|
|
45
54
|
const [creatingRequest, setCreatingRequest] = useState(false);
|
|
@@ -51,41 +60,44 @@ function MatterDetailContent({ uid }) {
|
|
|
51
60
|
const [materialText, setMaterialText] = useState("");
|
|
52
61
|
const [materialError, setMaterialError] = useState(null);
|
|
53
62
|
const [loadingMaterial, setLoadingMaterial] = useState(false);
|
|
54
|
-
|
|
63
|
+
// Bumped whenever the material modal closes (or the matter does), so a slow "Get material" response for a
|
|
64
|
+
// modal that's already gone - or for an earlier request - is never shown.
|
|
65
|
+
const materialSeq = useRef(0);
|
|
55
66
|
const [creatingExport, setCreatingExport] = useState(false);
|
|
56
67
|
const [exportError, setExportError] = useState(null);
|
|
57
68
|
const [searchText, setSearchText] = useState("");
|
|
58
69
|
const [searching, setSearching] = useState(false);
|
|
59
70
|
const [searchError, setSearchError] = useState(null);
|
|
60
71
|
const [searchResults, setSearchResults] = useState(null);
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
// Bumped by every search and by closing the matter, so a search still in flight when the matter closes (or
|
|
73
|
+
// superseded by a newer search) never shows its results.
|
|
74
|
+
const searchSeq = useRef(0);
|
|
75
|
+
useEffect(() => {
|
|
65
76
|
setLoading(true);
|
|
66
77
|
setError(null);
|
|
67
|
-
|
|
68
|
-
.then(
|
|
69
|
-
setMatter(loadedMatter);
|
|
70
|
-
setRequests(allRequests.filter((r) => r.matterId === uid));
|
|
71
|
-
setExportRequests(allExportRequests.filter((r) => r.matterId === uid));
|
|
72
|
-
})
|
|
78
|
+
getMatter(uid)
|
|
79
|
+
.then(setMatter)
|
|
73
80
|
.catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load this matter."))
|
|
74
81
|
.finally(() => setLoading(false));
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
reload();
|
|
82
|
+
void requests.reload();
|
|
83
|
+
void exportRequests.reload();
|
|
78
84
|
}, [uid]);
|
|
79
|
-
// Only ever invoked from the
|
|
85
|
+
// Only ever invoked from the close-confirmation modal below, which itself only renders once `matter` is
|
|
80
86
|
// resolved (children only render once loaded — see the early returns further down) — the non-null
|
|
81
|
-
// assertion reflects that real invariant, not an unchecked assumption.
|
|
82
|
-
// handleVerify`'s identical pattern.
|
|
87
|
+
// assertion reflects that real invariant, not an unchecked assumption.
|
|
83
88
|
async function handleClose() {
|
|
84
89
|
setClosing(true);
|
|
85
90
|
setCloseError(null);
|
|
86
91
|
try {
|
|
87
92
|
const updated = await closeMatter(matter.uid);
|
|
88
93
|
setMatter(updated);
|
|
94
|
+
setConfirmingClose(false);
|
|
95
|
+
// Nothing more may be read under a closed matter - drop any material or search results already on
|
|
96
|
+
// screen, including a search still in flight, and any approval still being confirmed.
|
|
97
|
+
closeMaterialModal();
|
|
98
|
+
searchSeq.current++;
|
|
99
|
+
setSearchResults(null);
|
|
100
|
+
setSearching(false);
|
|
89
101
|
}
|
|
90
102
|
catch (err) {
|
|
91
103
|
setCloseError(err instanceof ApiRequestError ? err.message : "Could not close this matter.");
|
|
@@ -94,6 +106,10 @@ function MatterDetailContent({ uid }) {
|
|
|
94
106
|
setClosing(false);
|
|
95
107
|
}
|
|
96
108
|
}
|
|
109
|
+
function closeCloseModal() {
|
|
110
|
+
setConfirmingClose(false);
|
|
111
|
+
setCloseError(null);
|
|
112
|
+
}
|
|
97
113
|
async function handleCreateRequest(e) {
|
|
98
114
|
e.preventDefault();
|
|
99
115
|
setNewRequestError(null);
|
|
@@ -106,7 +122,7 @@ function MatterDetailContent({ uid }) {
|
|
|
106
122
|
await createAccessRequest({ matterId: uid, mailboxUid: newRequestMailboxUid.trim() });
|
|
107
123
|
setShowNewRequest(false);
|
|
108
124
|
setNewRequestMailboxUid("");
|
|
109
|
-
reload();
|
|
125
|
+
void requests.reload();
|
|
110
126
|
}
|
|
111
127
|
catch (err) {
|
|
112
128
|
setNewRequestError(err instanceof ApiRequestError ? err.message : "Could not create this access request.");
|
|
@@ -115,12 +131,15 @@ function MatterDetailContent({ uid }) {
|
|
|
115
131
|
setCreatingRequest(false);
|
|
116
132
|
}
|
|
117
133
|
}
|
|
118
|
-
|
|
134
|
+
// Only ever invoked from the approve-confirmation modal below, which only renders once `approveTarget` is set.
|
|
135
|
+
async function handleApprove() {
|
|
136
|
+
const request = approveTarget;
|
|
137
|
+
setApproveTarget(null);
|
|
119
138
|
setActingOn((prev) => ({ ...prev, [request.uid]: true }));
|
|
120
139
|
setActionErrors((prev) => ({ ...prev, [request.uid]: "" }));
|
|
121
140
|
try {
|
|
122
141
|
const updated = await approveAccessRequest(request.uid);
|
|
123
|
-
|
|
142
|
+
requests.replaceItem(updated);
|
|
124
143
|
}
|
|
125
144
|
catch (err) {
|
|
126
145
|
setActionErrors((prev) => ({
|
|
@@ -137,7 +156,7 @@ function MatterDetailContent({ uid }) {
|
|
|
137
156
|
setActionErrors((prev) => ({ ...prev, [request.uid]: "" }));
|
|
138
157
|
try {
|
|
139
158
|
const updated = await denyAccessRequest(request.uid);
|
|
140
|
-
|
|
159
|
+
requests.replaceItem(updated);
|
|
141
160
|
}
|
|
142
161
|
catch (err) {
|
|
143
162
|
setActionErrors((prev) => ({
|
|
@@ -150,30 +169,39 @@ function MatterDetailContent({ uid }) {
|
|
|
150
169
|
}
|
|
151
170
|
}
|
|
152
171
|
async function handleGetMaterial(request) {
|
|
172
|
+
const seq = ++materialSeq.current;
|
|
153
173
|
setMaterialFor(request);
|
|
154
174
|
setMaterialText("");
|
|
155
175
|
setMaterialError(null);
|
|
156
176
|
setLoadingMaterial(true);
|
|
157
177
|
try {
|
|
158
178
|
const material = await getAccessRequestMaterial(request.uid);
|
|
159
|
-
|
|
179
|
+
if (seq === materialSeq.current)
|
|
180
|
+
setMaterialText(JSON.stringify(material.masterKeyWraps, null, 2));
|
|
160
181
|
}
|
|
161
182
|
catch (err) {
|
|
162
|
-
|
|
183
|
+
if (seq === materialSeq.current) {
|
|
184
|
+
setMaterialError(err instanceof ApiRequestError ? err.message : "Could not read this request's material.");
|
|
185
|
+
}
|
|
163
186
|
}
|
|
164
187
|
finally {
|
|
165
|
-
|
|
188
|
+
if (seq === materialSeq.current)
|
|
189
|
+
setLoadingMaterial(false);
|
|
166
190
|
}
|
|
167
191
|
}
|
|
168
192
|
function closeMaterialModal() {
|
|
193
|
+
materialSeq.current++;
|
|
169
194
|
setMaterialFor(null);
|
|
195
|
+
setMaterialText("");
|
|
196
|
+
setMaterialError(null);
|
|
197
|
+
setLoadingMaterial(false);
|
|
170
198
|
}
|
|
171
199
|
async function handleCreateExport() {
|
|
172
200
|
setExportError(null);
|
|
173
201
|
setCreatingExport(true);
|
|
174
202
|
try {
|
|
175
203
|
await createMatterExportRequest(uid);
|
|
176
|
-
reload();
|
|
204
|
+
void exportRequests.reload();
|
|
177
205
|
}
|
|
178
206
|
catch (err) {
|
|
179
207
|
setExportError(err instanceof ApiRequestError ? err.message : "Could not start this export.");
|
|
@@ -184,6 +212,7 @@ function MatterDetailContent({ uid }) {
|
|
|
184
212
|
}
|
|
185
213
|
async function handleSearch(e) {
|
|
186
214
|
e.preventDefault();
|
|
215
|
+
const seq = ++searchSeq.current;
|
|
187
216
|
setSearchError(null);
|
|
188
217
|
setSearching(true);
|
|
189
218
|
try {
|
|
@@ -207,13 +236,17 @@ function MatterDetailContent({ uid }) {
|
|
|
207
236
|
flags: parsed.flags,
|
|
208
237
|
labels: parsed.labels,
|
|
209
238
|
});
|
|
210
|
-
|
|
239
|
+
if (seq === searchSeq.current)
|
|
240
|
+
setSearchResults(results);
|
|
211
241
|
}
|
|
212
242
|
catch (err) {
|
|
213
|
-
|
|
243
|
+
if (seq === searchSeq.current) {
|
|
244
|
+
setSearchError(err instanceof ApiRequestError ? err.message : "Could not search this matter.");
|
|
245
|
+
}
|
|
214
246
|
}
|
|
215
247
|
finally {
|
|
216
|
-
|
|
248
|
+
if (seq === searchSeq.current)
|
|
249
|
+
setSearching(false);
|
|
217
250
|
}
|
|
218
251
|
}
|
|
219
252
|
if (loading) {
|
|
@@ -222,13 +255,15 @@ function MatterDetailContent({ uid }) {
|
|
|
222
255
|
if (error || !matter) {
|
|
223
256
|
return _jsx(Alert, { children: error ?? "Matter not found." });
|
|
224
257
|
}
|
|
225
|
-
|
|
258
|
+
const matterRequests = requests.items.filter((request) => request.matterId === uid);
|
|
259
|
+
const matterExportRequests = exportRequests.items.filter((request) => request.matterId === uid);
|
|
260
|
+
return (_jsxs("div", { className: "max-w-4xl flex flex-col gap-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx("a", { href: "/escrow", className: "text-sm text-primary-dark hover:underline", children: "\u2190 All matters" }), _jsx("h1", { className: "text-xl font-bold tracking-tight mt-1", children: matter.name })] }), !matter.closedAt && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto shrink-0", onClick: () => setConfirmingClose(true), children: "Close matter" }))] }), _jsx("div", { className: "bg-surface border border-border rounded-md p-6", children: _jsxs("dl", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-3 text-sm", children: [_jsx("dt", { className: "text-text-muted", children: "Description" }), _jsx("dd", { children: matter.description ?? "None" }), _jsx("dt", { className: "text-text-muted", children: "Escrow scope" }), _jsx("dd", { className: "font-mono text-xs", children: matter.escrowScopeId }), _jsx("dt", { className: "text-text-muted", children: "Custodian mailboxes" }), _jsx("dd", { children: matter.custodianMailboxUids.join(", ") }), _jsx("dt", { className: "text-text-muted", children: "Date range" }), _jsx("dd", { children: formatDateRange(matter) }), _jsx("dt", { className: "text-text-muted", children: "Status" }), _jsx("dd", { children: matter.closedAt ? `Closed ${new Date(matter.closedAt).toLocaleString()}` : "Open" })] }) }), _jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide", children: "Access requests" }), !matter.closedAt && (_jsx(Button, { type: "button", className: "!w-auto", onClick: () => setShowNewRequest(true), children: "+ New access request" }))] }), requests.loadError && _jsx(Alert, { children: requests.loadError }), matterRequests.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: requests.loading ? "Loading…" : "No access requests yet." })) : (_jsx("ul", { className: "flex flex-col gap-3", children: matterRequests.map((request) => (_jsxs("li", { className: "border border-border rounded-sm p-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 flex-wrap", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold", children: request.mailboxUid }), _jsxs("div", { className: "text-xs text-text-muted", children: [request.approvals.length, " of ", request.requiredHoldersAtCreation, " approvals \u00B7 requested by ", request.requestedByUserUid] })] }), _jsx("span", { className: [
|
|
226
261
|
"text-xs font-bold uppercase tracking-wide py-0.5 px-2 rounded-pill",
|
|
227
262
|
request.status === "denied"
|
|
228
263
|
? "bg-danger-bg text-danger"
|
|
229
264
|
: request.status === "pending"
|
|
230
265
|
? "bg-surface-alt text-text-muted"
|
|
231
266
|
: "bg-success text-white",
|
|
232
|
-
].join(" "), children: request.status })] }), actionErrors[request.uid] && _jsx(Alert, { children: actionErrors[request.uid] }), _jsxs("div", { className: "flex gap-3 mt-3", children: [request.status === "pending" && (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", className: "!w-auto", loading: actingOn[request.uid], disabled: actingOn[request.uid], onClick: () =>
|
|
233
|
-
(Object.keys(searchResults).length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No custodian mailboxes could be searched." })) : (_jsx("div", { className: "flex flex-col gap-4", children: Object.entries(searchResults).map(([mailboxUid, page]) => (_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold mb-2", children: mailboxUid }), page.results.length === 0 ? (_jsx("p", { className: "text-xs text-text-muted", children: "No matches." })) : (_jsx("ul", { className: "flex flex-col gap-1", children: page.results.map((result) => (_jsxs("li", { className: "text-sm py-1 px-2 bg-surface-alt rounded-sm", children: [result.entityType, " \u00B7 ", result.entityUid, result.snippet && _jsxs("span", { className: "text-text-muted", children: [" \u2014 ", result.snippet] })] }, `${result.entityType}-${result.entityUid}`))) }))] }, mailboxUid))) })))] }), _jsx(Modal, { open: showNewRequest, onClose: () => setShowNewRequest(false), title: "New access request", children: _jsxs("form", { onSubmit: handleCreateRequest, className: "flex flex-col gap-4", children: [newRequestError && _jsx(Alert, { children: newRequestError }), _jsx(FormField, { label: "Mailbox uid", htmlFor: "newRequestMailboxUid", children: _jsx("input", { id: "newRequestMailboxUid", type: "text", className: INPUT_CLASS, value: newRequestMailboxUid, onChange: (e) => setNewRequestMailboxUid(e.target.value) }) }), _jsx("p", { className: "text-xs text-text-muted", children: "Must be one of this matter's own custodian mailboxes, currently assigned to this matter's escrow scope. Your own creation counts as the first approval." }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: creatingRequest, onClick: () => setShowNewRequest(false), children: "Cancel" }), _jsx(Button, { type: "submit", className: "!w-auto", loading: creatingRequest, disabled: creatingRequest, children: "Create request" })] })] }) }), _jsx(Modal, { open: materialFor !== null, onClose: closeMaterialModal, title: "Escrow key material", children: loadingMaterial ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : materialError ? (_jsx(Alert, { children: materialError })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-sm text-text-muted mb-3", children: ["Still-encrypted master key wraps for ", _jsx("strong", { children: materialFor?.mailboxUid }), " \u2014 this server cannot decrypt these either. Copy the JSON below into your own offline tooling."] }), _jsx("pre", { className: "text-xs font-mono bg-surface-alt border border-border rounded-sm p-3 overflow-x-auto whitespace-pre-wrap break-all", children: materialText })] })) })] }));
|
|
267
|
+
].join(" "), children: request.status })] }), actionErrors[request.uid] && _jsx(Alert, { children: actionErrors[request.uid] }), _jsxs("div", { className: "flex gap-3 mt-3", children: [!matter.closedAt && request.status === "pending" && (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", className: "!w-auto", loading: actingOn[request.uid], disabled: actingOn[request.uid], onClick: () => setApproveTarget(request), children: "Approve" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", loading: actingOn[request.uid], disabled: actingOn[request.uid], onClick: () => handleDeny(request), children: "Deny" })] })), !matter.closedAt && (request.status === "approved" || request.status === "fulfilled") && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => handleGetMaterial(request), children: "Get material" }))] })] }, request.uid))) })), _jsx(LoadMoreButton, { list: requests, label: "Load more access requests" })] }), _jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide", children: "Export this matter" }), !matter.closedAt && (_jsx(Button, { type: "button", className: "!w-auto", loading: creatingExport, disabled: creatingExport, onClick: handleCreateExport, children: "+ New export" }))] }), _jsx("p", { className: "text-xs text-text-muted mb-3", children: "Exports messages (narrowed to this matter\u2019s own date range), plus full contacts, calendar events, tasks, and notes, from every custodian mailbox actually assigned to this matter\u2019s escrow scope, as one combined NDJSON file." }), exportError && _jsx(Alert, { children: exportError }), exportRequests.loadError && _jsx(Alert, { children: exportRequests.loadError }), matterExportRequests.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: exportRequests.loading ? "Loading…" : "No export requests yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: matterExportRequests.map((request) => (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsxs("span", { children: [new Date(request.dateCreated).toLocaleString(), request.status === "failed" && request.errorMessage && (_jsxs("span", { className: "text-danger", children: [" \u2014 ", request.errorMessage] }))] }), _jsxs("span", { className: "flex items-center gap-3", children: [_jsx("span", { className: `text-xs font-bold uppercase tracking-wide py-0.5 px-2 rounded-pill ${statusBadgeClass(request.status)}`, children: statusLabel(request.status) }), !matter.closedAt && request.status === "ready" && (_jsx("a", { href: matterExportRequestDownloadUrl(request.uid), className: "text-primary-dark hover:underline font-medium", children: "Download" }))] })] }, request.uid))) }))] }), _jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide mb-3", children: "Search this matter\u2019s custodians" }), matter.closedAt ? (_jsx("p", { className: "text-sm text-text-muted", children: "This matter is closed, so its custodians can no longer be searched or exported." })) : (_jsxs("form", { onSubmit: handleSearch, className: "flex gap-3 mb-4", children: [_jsx("input", { "aria-label": "Search this matter", className: INPUT_CLASS, value: searchText, onChange: (e) => setSearchText(e.target.value), placeholder: "Free text or an operator filter, e.g. from:alice@example.com" }), _jsx(Button, { type: "submit", className: "!w-auto shrink-0", loading: searching, disabled: searching || !searchText.trim(), children: "Search" })] })), searchError && _jsx(Alert, { children: searchError }), searchResults &&
|
|
268
|
+
(Object.keys(searchResults).length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No custodian mailboxes could be searched." })) : (_jsx("div", { className: "flex flex-col gap-4", children: Object.entries(searchResults).map(([mailboxUid, page]) => (_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold mb-2", children: mailboxUid }), page.results.length === 0 ? (_jsx("p", { className: "text-xs text-text-muted", children: "No matches." })) : (_jsx("ul", { className: "flex flex-col gap-1", children: page.results.map((result) => (_jsxs("li", { className: "text-sm py-1 px-2 bg-surface-alt rounded-sm", children: [result.entityType, " \u00B7 ", result.entityUid, result.snippet && _jsxs("span", { className: "text-text-muted", children: [" \u2014 ", result.snippet] })] }, `${result.entityType}-${result.entityUid}`))) }))] }, mailboxUid))) })))] }), _jsxs(Modal, { open: confirmingClose, onClose: closeCloseModal, title: "Close matter", children: [_jsxs("p", { className: "text-sm mb-3", children: ["Close ", _jsx("strong", { children: matter.name }), "? Once closed, no new access requests, exports, searches, or key material reads are allowed under it. This cannot be reopened here."] }), _jsxs("dl", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-sm mb-4", children: [_jsx("dt", { className: "text-text-muted", children: "Custodians" }), _jsx("dd", { className: "break-all", children: matter.custodianMailboxUids.join(", ") }), _jsx("dt", { className: "text-text-muted", children: "Date range" }), _jsx("dd", { children: formatDateRange(matter) })] }), closeError && _jsx(Alert, { children: closeError }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: closing, onClick: closeCloseModal, children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", loading: closing, disabled: closing, onClick: handleClose, children: "Close matter" })] })] }), _jsxs(Modal, { open: approveTarget !== null, onClose: () => setApproveTarget(null), title: "Approve access request", children: [_jsx("p", { className: "text-sm mb-3", children: "Approving lets holders of this matter\u2019s escrow scope read the key material for this mailbox once enough holders have approved." }), _jsxs("dl", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-sm mb-4", children: [_jsx("dt", { className: "text-text-muted", children: "Matter" }), _jsx("dd", { children: matter.name }), _jsx("dt", { className: "text-text-muted", children: "Mailbox" }), _jsx("dd", { className: "break-all", children: approveTarget?.mailboxUid }), _jsx("dt", { className: "text-text-muted", children: "Date range" }), _jsx("dd", { children: formatDateRange(matter) }), _jsx("dt", { className: "text-text-muted", children: "Approvals" }), _jsxs("dd", { children: [approveTarget?.approvals.length, " of ", approveTarget?.requiredHoldersAtCreation, " so far, including requester ", approveTarget?.requestedByUserUid] })] }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setApproveTarget(null), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto", onClick: () => void handleApprove(), children: "Approve request" })] })] }), _jsx(Modal, { open: showNewRequest, onClose: () => setShowNewRequest(false), title: "New access request", children: _jsxs("form", { onSubmit: handleCreateRequest, className: "flex flex-col gap-4", children: [newRequestError && _jsx(Alert, { children: newRequestError }), _jsx(FormField, { label: "Mailbox uid", htmlFor: "newRequestMailboxUid", children: _jsx("input", { id: "newRequestMailboxUid", type: "text", className: INPUT_CLASS, value: newRequestMailboxUid, onChange: (e) => setNewRequestMailboxUid(e.target.value) }) }), _jsx("p", { className: "text-xs text-text-muted", children: "Must be one of this matter's own custodian mailboxes, currently assigned to this matter's escrow scope. Your own creation counts as the first approval." }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: creatingRequest, onClick: () => setShowNewRequest(false), children: "Cancel" }), _jsx(Button, { type: "submit", className: "!w-auto", loading: creatingRequest, disabled: creatingRequest, children: "Create request" })] })] }) }), _jsx(Modal, { open: materialFor !== null, onClose: closeMaterialModal, title: "Escrow key material", children: loadingMaterial ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : materialError ? (_jsx(Alert, { children: materialError })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-sm text-text-muted mb-3", children: ["Still-encrypted master key wraps for ", _jsx("strong", { children: materialFor?.mailboxUid }), " \u2014 this server cannot decrypt these either. Copy the JSON below into your own offline tooling."] }), _jsx("pre", { className: "text-xs font-mono bg-surface-alt border border-border rounded-sm p-3 overflow-x-auto whitespace-pre-wrap break-all", children: materialText })] })) })] }));
|
|
234
269
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DistributionList } from "@rapidmx/react-shared/admin/distributionListsApi.js";
|
|
3
|
+
export interface MemberListCardProps {
|
|
4
|
+
list: DistributionList;
|
|
5
|
+
/** Called with the freshly-saved list (new `version`, updated `memberAddresses`) after a successful
|
|
6
|
+
* add/remove, so the parent detail page can keep its own copy of the list in sync — mirrors
|
|
7
|
+
* `ShareAccessCard`'s `reload()` pattern, just pushed up rather than re-fetched, since the caller
|
|
8
|
+
* already has everything the response would return. */
|
|
9
|
+
onUpdate: (list: DistributionList) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Lets an admin view and edit a distribution list's members — same load/inline-add-row/per-row-remove
|
|
13
|
+
* interaction shape as `ShareAccessCard`, but against a plain `memberAddresses: string[]` field on the
|
|
14
|
+
* `DistributionList` itself via read-modify-write `PUT`, not a separate ACL endpoint (a distribution
|
|
15
|
+
* list has no per-record ACL to speak of — see `BaseDistributionListRoute`'s own doc comment).
|
|
16
|
+
*/
|
|
17
|
+
export default function MemberListCard({ list, onUpdate }: MemberListCardProps): React.JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/** The part of an `EscrowScope` shared by the "new" and "[uid]" pages — everything except `name`/
|
|
3
|
+
* `description`, which stay plain inline `FormField`s on each page (same split `distribution-lists`
|
|
4
|
+
* already uses: simple top-level fields inline, the more involved sub-structure factored out). An admin
|
|
5
|
+
* pastes in the fields of an already-issued certificate here; nothing in this app generates a keypair —
|
|
6
|
+
* see `EscrowScopePublicKey`'s own doc comment in `escrowScopesApi.ts`. */
|
|
7
|
+
export interface EscrowScopeKeyAndHoldersValue {
|
|
8
|
+
publicKey: string;
|
|
9
|
+
keyType: string;
|
|
10
|
+
fingerprint: string;
|
|
11
|
+
/** `<input type="datetime-local">` value — see `toDatetimeLocal()`. */
|
|
12
|
+
notBefore: string;
|
|
13
|
+
notAfter: string;
|
|
14
|
+
holderUserUids: string[];
|
|
15
|
+
requiredHolders: number;
|
|
16
|
+
notifySubjectOnAccess: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface EscrowScopeKeyAndHoldersFieldsProps {
|
|
19
|
+
value: EscrowScopeKeyAndHoldersValue;
|
|
20
|
+
onChange: (value: EscrowScopeKeyAndHoldersValue) => void;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Shows the public key fields without letting them be edited, e.g. for keys generated in the browser. */
|
|
23
|
+
keyReadOnly?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Builds an all-zeroes starting point for the "new" page — `notBefore`/`notAfter` default to "now" so an
|
|
26
|
+
* admin pasting in a cert's own dates only has to touch what actually differs. */
|
|
27
|
+
export declare function emptyEscrowScopeKeyAndHoldersValue(): EscrowScopeKeyAndHoldersValue;
|
|
28
|
+
/** The message shown when an administrator tries to make themselves a holder: whoever configures a scope must not
|
|
29
|
+
* also be able to approve access under it (separation of duties - the server refuses it too). Holders who were
|
|
30
|
+
* already on the scope (`existingHolderUserUids`) aren't treated as newly added. */
|
|
31
|
+
export declare function selfAsHolderError(holderUserUids: string[], adminUid: string | undefined, existingHolderUserUids?: string[]): string | null;
|
|
32
|
+
export default function EscrowScopeKeyAndHoldersFields({ value, onChange, disabled, keyReadOnly }: EscrowScopeKeyAndHoldersFieldsProps): React.JSX.Element;
|
|
@@ -18,9 +18,18 @@ export function emptyEscrowScopeKeyAndHoldersValue() {
|
|
|
18
18
|
notifySubjectOnAccess: false,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
/** The message shown when an administrator tries to make themselves a holder: whoever configures a scope must not
|
|
22
|
+
* also be able to approve access under it (separation of duties - the server refuses it too). Holders who were
|
|
23
|
+
* already on the scope (`existingHolderUserUids`) aren't treated as newly added. */
|
|
24
|
+
export function selfAsHolderError(holderUserUids, adminUid, existingHolderUserUids = []) {
|
|
25
|
+
if (!adminUid || !holderUserUids.includes(adminUid) || existingHolderUserUids.includes(adminUid)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return "You can't add yourself as a holder of an escrow scope you configure. Ask another administrator to add you, or choose different holders.";
|
|
29
|
+
}
|
|
30
|
+
export default function EscrowScopeKeyAndHoldersFields({ value, onChange, disabled, keyReadOnly }) {
|
|
22
31
|
function set(key, next) {
|
|
23
32
|
onChange({ ...value, [key]: next });
|
|
24
33
|
}
|
|
25
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide mb-1", children: "Public key" }), _jsx("p", { className: "text-sm text-text-muted mb-4", children: "The fields of an already-issued certificate \u2014 this server never generates or holds the corresponding private key." }), _jsx(FormField, { label: "Public key (base64)", htmlFor: "publicKey", children: _jsx("textarea", { id: "publicKey", className: `${INPUT_CLASS} font-mono text-xs`, rows: 4, value: value.publicKey, disabled: disabled, onChange: (e) => set("publicKey", e.target.value) }) }), _jsx(FormField, { label: "Key type", htmlFor: "keyType", children: _jsx("input", { id: "keyType", type: "text", className: INPUT_CLASS, value: value.keyType, disabled: disabled, onChange: (e) => set("keyType", e.target.value), placeholder: "x509" }) }), _jsx(FormField, { label: "Fingerprint (hex SHA-256)", htmlFor: "fingerprint", children: _jsx("input", { id: "fingerprint", type: "text", className: `${INPUT_CLASS} font-mono`, value: value.fingerprint, disabled: disabled, onChange: (e) => set("fingerprint", e.target.value) }) }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-4", children: [_jsx(FormField, { label: "Not before", htmlFor: "notBefore", children: _jsx("input", { id: "notBefore", type: "datetime-local", className: INPUT_CLASS, value: value.notBefore, disabled: disabled, onChange: (e) => set("notBefore", e.target.value) }) }), _jsx(FormField, { label: "Not after", htmlFor: "notAfter", children: _jsx("input", { id: "notAfter", type: "datetime-local", className: INPUT_CLASS, value: value.notAfter, disabled: disabled, onChange: (e) => set("notAfter", e.target.value) }) })] })] }), _jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide mb-1", children: "Holders & dual control" }), _jsx("p", { className: "text-sm text-text-muted mb-4", children: "Every uid below is granted the eDiscovery/compliance holder role for this scope \u2014 a deliberately separate role from server administration. At least one holder is required." }), _jsx(StringListField, { label: "Holder user uids", id: "holderUserUids", values: value.holderUserUids, onChange: (holderUserUids) => set("holderUserUids", holderUserUids), placeholder: "User uid to add", emptyMessage: "No holders added yet.", disabled: disabled }), _jsx(FormField, { label: "Required holders (M-of-N dual control)", htmlFor: "requiredHolders", children: _jsx("input", { id: "requiredHolders", type: "number", className: INPUT_CLASS, value: value.requiredHolders, disabled: disabled, onChange: (e) => set("requiredHolders", Number(e.target.value) || 1) }) }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: value.notifySubjectOnAccess, disabled: disabled, onChange: (e) => set("notifySubjectOnAccess", e.target.checked) }), "Notify subject on access"] })] })] }));
|
|
34
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide mb-1", children: "Public key" }), _jsx("p", { className: "text-sm text-text-muted mb-4", children: "The fields of an already-issued certificate \u2014 this server never generates or holds the corresponding private key." }), _jsx(FormField, { label: "Public key (base64)", htmlFor: "publicKey", children: _jsx("textarea", { id: "publicKey", className: `${INPUT_CLASS} font-mono text-xs`, rows: 4, value: value.publicKey, disabled: disabled, readOnly: keyReadOnly, onChange: (e) => set("publicKey", e.target.value) }) }), _jsx(FormField, { label: "Key type", htmlFor: "keyType", children: _jsx("input", { id: "keyType", type: "text", className: INPUT_CLASS, value: value.keyType, disabled: disabled, readOnly: keyReadOnly, onChange: (e) => set("keyType", e.target.value), placeholder: "x509" }) }), _jsx(FormField, { label: "Fingerprint (hex SHA-256)", htmlFor: "fingerprint", children: _jsx("input", { id: "fingerprint", type: "text", className: `${INPUT_CLASS} font-mono`, value: value.fingerprint, disabled: disabled, readOnly: keyReadOnly, onChange: (e) => set("fingerprint", e.target.value) }) }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-4", children: [_jsx(FormField, { label: "Not before", htmlFor: "notBefore", children: _jsx("input", { id: "notBefore", type: "datetime-local", className: INPUT_CLASS, value: value.notBefore, disabled: disabled, readOnly: keyReadOnly, onChange: (e) => set("notBefore", e.target.value) }) }), _jsx(FormField, { label: "Not after", htmlFor: "notAfter", children: _jsx("input", { id: "notAfter", type: "datetime-local", className: INPUT_CLASS, value: value.notAfter, disabled: disabled, readOnly: keyReadOnly, onChange: (e) => set("notAfter", e.target.value) }) })] })] }), _jsxs("div", { className: "bg-surface border border-border rounded-md p-6", children: [_jsx("h2", { className: "text-base font-bold uppercase tracking-wide mb-1", children: "Holders & dual control" }), _jsx("p", { className: "text-sm text-text-muted mb-4", children: "Every uid below is granted the eDiscovery/compliance holder role for this scope \u2014 a deliberately separate role from server administration. At least one holder is required." }), _jsx(StringListField, { label: "Holder user uids", id: "holderUserUids", values: value.holderUserUids, onChange: (holderUserUids) => set("holderUserUids", holderUserUids), placeholder: "User uid to add", emptyMessage: "No holders added yet.", disabled: disabled }), _jsx(FormField, { label: "Required holders (M-of-N dual control)", htmlFor: "requiredHolders", children: _jsx("input", { id: "requiredHolders", type: "number", className: INPUT_CLASS, value: value.requiredHolders, disabled: disabled, onChange: (e) => set("requiredHolders", Number(e.target.value) || 1) }) }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: value.notifySubjectOnAccess, disabled: disabled, onChange: (e) => set("notifySubjectOnAccess", e.target.checked) }), "Notify subject on access"] })] })] }));
|
|
26
35
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "../../../styles/app.css";
|
|
2
|
+
import React, { PropsWithChildren } from "react";
|
|
3
|
+
import { NavItem } from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
|
|
4
|
+
import { PluginNav, PluginNavProps } from "../../../plugins/pluginNav.js";
|
|
5
|
+
export type AdminSection = "mailboxes" | "quarantine" | "ingestQueue" | "domains" | "auditLog" | "distributionLists" | "transportRules" | "escrowScopes" | "retentionPolicy" | "encryptionPolicy" | "mailboxPolicy" | "setup" | "dataRequests" | "plugins" | "branding";
|
|
6
|
+
/** A core `AdminSection`, or a plugin's `adminNav` item id (see `PluginNav`). */
|
|
7
|
+
export type AdminShellActive = AdminSection | (string & {});
|
|
8
|
+
export interface AdminShellProps extends PluginNavProps {
|
|
9
|
+
/** Which icon in the rail is highlighted as the current section - a plugin admin page passes its own
|
|
10
|
+
* `adminNav` item id. */
|
|
11
|
+
active: AdminShellActive;
|
|
12
|
+
/** Populated automatically by the framework from an authenticated request (e.g. a valid `jwt` cookie). */
|
|
13
|
+
userUid?: string;
|
|
14
|
+
/** auth-server's base URL, injected via the route's `fetchProps` — see `src/mongo/routes/AdminConsoleRoute.ts`. */
|
|
15
|
+
authServerUrl?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Where `mailApi.ts`'s `impersonateUser()`/`stopImpersonating()` should call: the real auth-server in
|
|
18
|
+
* production, or `""` under `yarn dev` to call this app's own local dev-only impersonation endpoint
|
|
19
|
+
* instead — see `AdminConsoleRoute`'s `fetchProps` and `src/dev/DevImpersonationRoute.ts`.
|
|
20
|
+
*/
|
|
21
|
+
impersonationBaseUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
/** `NAV_ITEMS` followed by the plugins' `adminNav` items (generic icon). Core ids win, including the
|
|
24
|
+
* off-rail sections' - see `mergePluginNavItems`. */
|
|
25
|
+
export declare function adminNavItems(pluginNav?: PluginNav): NavItem[];
|
|
26
|
+
/**
|
|
27
|
+
* Gates every `apps/admin` page behind the `admin` trusted role. Uses `GET /api/admin/release-notes` (any
|
|
28
|
+
* `BaseAdminRoute` endpoint works — this one is side-effect-free) purely as a canary: a 200 means the
|
|
29
|
+
* caller's JWT carries a trusted role, a 403 means it doesn't. There is no local step-up/elevation flow
|
|
30
|
+
* (that would need a cross-origin call to auth-server's own elevation endpoint — not wired up yet).
|
|
31
|
+
*/
|
|
32
|
+
export default function AdminShell({ active, userUid, authServerUrl, pluginNav, children }: PropsWithChildren<AdminShellProps>): React.JSX.Element;
|
|
@@ -14,6 +14,8 @@ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
|
14
14
|
import BottomTabBar from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
|
|
15
15
|
import { BrandingFooter, BrandingHeader } from "../../layout/BrandingChrome.js";
|
|
16
16
|
import UserMenu from "../../layout/UserMenu.js";
|
|
17
|
+
import { signOutOfConsole } from "../signOut.js";
|
|
18
|
+
import { mergePluginNavItems } from "../../../plugins/pluginNav.js";
|
|
17
19
|
/** Sections shown in the persistent icon rail / mobile tab bar — every admin area reachable from
|
|
18
20
|
* anywhere in the console. Deliberately excludes `quarantine`/`ingestQueue`: those are scoped to a
|
|
19
21
|
* single mailbox and only ever reached via links on that mailbox's own detail page (see
|
|
@@ -75,14 +77,19 @@ const MAILBOX_SCOPED_ITEMS = [
|
|
|
75
77
|
];
|
|
76
78
|
/** The setup wizard - reached by redirect or from the Mailboxes page, not from the rail. */
|
|
77
79
|
const SETUP_ITEM = { id: "setup", href: "/admin/setup", label: "Setup", icon: HiOutlineRocketLaunch };
|
|
78
|
-
const
|
|
80
|
+
const OFF_RAIL_ITEMS = [...MAILBOX_SCOPED_ITEMS, SETUP_ITEM];
|
|
81
|
+
/** `NAV_ITEMS` followed by the plugins' `adminNav` items (generic icon). Core ids win, including the
|
|
82
|
+
* off-rail sections' - see `mergePluginNavItems`. */
|
|
83
|
+
export function adminNavItems(pluginNav) {
|
|
84
|
+
return mergePluginNavItems(NAV_ITEMS, pluginNav?.adminNav, ({ id, href, label }) => ({ id, href, label, icon: HiOutlinePuzzlePiece }), OFF_RAIL_ITEMS.map((item) => item.id));
|
|
85
|
+
}
|
|
79
86
|
/**
|
|
80
87
|
* Gates every `apps/admin` page behind the `admin` trusted role. Uses `GET /api/admin/release-notes` (any
|
|
81
88
|
* `BaseAdminRoute` endpoint works — this one is side-effect-free) purely as a canary: a 200 means the
|
|
82
89
|
* caller's JWT carries a trusted role, a 403 means it doesn't. There is no local step-up/elevation flow
|
|
83
90
|
* (that would need a cross-origin call to auth-server's own elevation endpoint — not wired up yet).
|
|
84
91
|
*/
|
|
85
|
-
export default function AdminShell({ active, userUid, authServerUrl, children }) {
|
|
92
|
+
export default function AdminShell({ active, userUid, authServerUrl, pluginNav, children }) {
|
|
86
93
|
const [status, setStatus] = useState("checking");
|
|
87
94
|
const [error, setError] = useState(null);
|
|
88
95
|
const { branding, iconSrc } = useBranding();
|
|
@@ -118,7 +125,8 @@ export default function AdminShell({ active, userUid, authServerUrl, children })
|
|
|
118
125
|
});
|
|
119
126
|
}, [userUid]);
|
|
120
127
|
function handleSignOut() {
|
|
121
|
-
|
|
128
|
+
// Ends the auth-server session and tells other tabs, not just navigates - see `signOutOfConsole()`.
|
|
129
|
+
void signOutOfConsole(authServerUrl);
|
|
122
130
|
}
|
|
123
131
|
let content;
|
|
124
132
|
if (!userUid || status === "checking") {
|
|
@@ -131,13 +139,14 @@ export default function AdminShell({ active, userUid, authServerUrl, children })
|
|
|
131
139
|
content = (_jsx("div", { className: "min-h-screen flex items-center justify-center p-8", children: _jsx("div", { className: "w-full max-w-md", children: _jsx(Alert, { children: error }) }) }));
|
|
132
140
|
}
|
|
133
141
|
else {
|
|
134
|
-
const
|
|
135
|
-
|
|
142
|
+
const navItems = adminNavItems(pluginNav);
|
|
143
|
+
const activeItem = [...navItems, ...OFF_RAIL_ITEMS].find((item) => item.id === active);
|
|
144
|
+
content = (_jsx("div", { className: "min-h-screen flex flex-col bg-surface-alt", children: _jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsxs("nav", { "aria-label": "Admin sections", className: "hidden md:flex w-16 shrink-0 bg-surface border-r border-border flex-col items-center py-3 gap-1", children: [_jsx("img", { src: iconSrc, width: "96", height: "96", alt: "", className: "mb-3" }), navItems.map(({ id, href, label, icon: Icon }) => (_jsx("a", { href: href, "aria-label": label, "aria-current": id === active ? "page" : undefined, title: label, className: [
|
|
136
145
|
"w-10 h-10 flex items-center justify-center rounded-sm",
|
|
137
146
|
id === active
|
|
138
147
|
? "bg-primary/10 text-primary-dark"
|
|
139
148
|
: "text-text-muted hover:bg-surface-alt hover:text-text",
|
|
140
|
-
].join(" "), children: _jsx(Icon, { size: 20, "aria-hidden": "true" }) }, id)))] }), _jsx(BottomTabBar, { apps:
|
|
149
|
+
].join(" "), children: _jsx(Icon, { size: 20, "aria-hidden": "true" }) }, id)))] }), _jsx(BottomTabBar, { apps: navItems, active: active }), _jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [_jsxs("header", { className: "h-16 shrink-0 bg-surface border-b border-border flex items-center justify-between gap-4 px-6", children: [_jsx("span", { className: "font-display font-bold text-lg uppercase tracking-wide", children: activeItem?.label }), _jsx(UserMenu, { userUid: userUid, authServerUrl: authServerUrl, onSignOut: handleSignOut })] }), _jsx("div", { className: "flex-1 pb-14 md:pb-0", children: _jsx("main", { className: "max-w-6xl mx-auto px-6 py-8", children: children }) })] })] }) }));
|
|
141
150
|
}
|
|
142
151
|
return (_jsxs(_Fragment, { children: [_jsx(BrandingHeader, { branding: branding }), content, _jsx(BrandingFooter, { branding: branding })] }));
|
|
143
152
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
3
|
+
export interface EscrowScopeCardProps {
|
|
4
|
+
mailbox: Mailbox;
|
|
5
|
+
/** Called with the saved mailbox, same push-up pattern as `ResourceSettingsCard`. */
|
|
6
|
+
onUpdate: (mailbox: Mailbox) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Assigns a mailbox to an escrow scope (or none), which decides whose holders can recover its encrypted mail. Only
|
|
10
|
+
* shown in the admin console, whose trusted-role gate the server enforces again for `escrowScopeId`.
|
|
11
|
+
*/
|
|
12
|
+
export default function EscrowScopeCard({ mailbox, onUpdate }: EscrowScopeCardProps): React.JSX.Element;
|