@rapidmx/web-client 0.30.0 → 0.32.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/apps/admin/audit-log/index.tsx +3 -3
- package/apps/admin/branding/index.tsx +3 -3
- package/apps/admin/data-requests/index.tsx +18 -7
- package/apps/admin/distribution-lists/[uid].tsx +3 -3
- package/apps/admin/distribution-lists/index.tsx +3 -3
- package/apps/admin/distribution-lists/new/index.tsx +3 -3
- package/apps/admin/domains/[uid].tsx +6 -5
- package/apps/admin/domains/index.tsx +3 -3
- package/apps/admin/domains/new/index.tsx +3 -3
- package/apps/admin/escrow-scopes/[uid].tsx +11 -5
- package/apps/admin/escrow-scopes/index.tsx +3 -3
- package/apps/admin/escrow-scopes/new/index.tsx +9 -3
- package/apps/admin/index.tsx +4 -4
- package/apps/admin/ingest-queue/index.tsx +3 -3
- package/apps/admin/mailboxes/[uid].tsx +15 -7
- package/apps/admin/quarantine/index.tsx +4 -4
- package/apps/admin/retention-policy/index.tsx +3 -3
- package/apps/admin/signing-certificates/index.tsx +3 -3
- package/apps/admin/transport-rules/[uid].tsx +4 -4
- package/apps/admin/transport-rules/index.tsx +3 -3
- package/apps/admin/transport-rules/new/index.tsx +3 -3
- package/apps/escrow/matters/[uid].tsx +26 -2
- package/apps/escrow/matters/new/index.tsx +4 -0
- package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
- package/apps/shared/calendar/useCalendarReminders.ts +2 -0
- package/apps/shared/components/admin/ActionAlert.tsx +32 -0
- package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +3 -3
- package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +2 -2
- package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
- package/apps/shared/components/admin/elevation.ts +19 -0
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
- package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
- package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
- package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
- package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
- package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
- package/apps/shared/components/admin/settings/PluginsManager.tsx +195 -46
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
- package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
- package/apps/shared/components/admin/signOut.ts +3 -0
- package/apps/shared/components/admin/usePagedList.tsx +2 -2
- package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
- package/apps/shared/components/calendar/EventDetails.tsx +34 -8
- package/apps/shared/components/calendar/EventEditor.tsx +90 -40
- package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
- package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
- package/apps/shared/components/calendar/EventModal.tsx +25 -1
- package/apps/shared/components/calendar/FindATime.tsx +10 -2
- package/apps/shared/components/calendar/ListView.tsx +99 -25
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
- package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
- package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
- package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
- package/apps/shared/components/calendar/gridLayout.ts +94 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
- package/apps/shared/components/calendar/timePicker.ts +5 -2
- package/apps/shared/components/contacts/ContactCard.tsx +15 -4
- package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
- package/apps/shared/components/contacts/ContactForm.tsx +61 -17
- package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
- package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
- package/apps/shared/components/contacts/participantDetails.ts +18 -1
- package/apps/shared/components/layout/AppShell.tsx +9 -0
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
- package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
- package/apps/shared/components/mail/ConversationList.tsx +49 -6
- package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
- package/apps/shared/components/mail/MenuButton.tsx +3 -400
- package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
- package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
- package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
- package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
- package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
- package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
- package/apps/shared/components/mail/writableMailboxes.ts +2 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
- package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
- package/apps/shared/components/tasks/dueDate.ts +29 -0
- package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
- package/apps/shared/mail/decryptedMessages.ts +51 -2
- package/apps/shared/mail/listSnapshots.ts +2 -0
- package/apps/shared/mail/outbox/sendDecision.ts +14 -3
- package/apps/shared/mail/outbox/sendJob.ts +5 -0
- package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
- package/apps/shared/mail/usePermanentDelete.tsx +3 -1
- package/apps/shared/notifications/apiErrors.ts +2 -0
- package/apps/shared/notifications/preferences.ts +1 -1
- package/apps/shared/notifications/store.ts +3 -1
- package/apps/shared/plugins/pluginNav.ts +4 -2
- package/apps/www/calendar/index.tsx +24 -12
- package/apps/www/contacts/[uid].tsx +6 -1
- package/apps/www/contacts/index.tsx +66 -12
- package/apps/www/index.tsx +91 -28
- package/apps/www/messages/[uid].tsx +8 -3
- package/apps/www/settings/auto-reply/index.tsx +13 -0
- package/apps/www/settings/blocked-senders/index.tsx +4 -0
- package/apps/www/settings/encryption/index.tsx +66 -4
- package/apps/www/settings/privacy/index.tsx +4 -1
- package/apps/www/settings/profile/index.tsx +12 -1
- package/apps/www/settings/sharing/index.tsx +11 -1
- package/apps/www/settings/signatures/[uid].tsx +6 -4
- package/apps/www/tasks/index.tsx +97 -29
- package/dist/apps/admin/audit-log/index.js +3 -3
- package/dist/apps/admin/branding/index.js +3 -3
- package/dist/apps/admin/data-requests/index.js +17 -7
- package/dist/apps/admin/distribution-lists/[uid].js +3 -3
- package/dist/apps/admin/distribution-lists/index.js +3 -3
- package/dist/apps/admin/distribution-lists/new/index.js +3 -3
- package/dist/apps/admin/domains/[uid].js +6 -5
- package/dist/apps/admin/domains/index.js +3 -3
- package/dist/apps/admin/domains/new/index.js +3 -3
- package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
- package/dist/apps/admin/escrow-scopes/index.js +3 -3
- package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
- package/dist/apps/admin/index.js +4 -4
- package/dist/apps/admin/ingest-queue/index.js +3 -3
- package/dist/apps/admin/mailboxes/[uid].js +15 -7
- package/dist/apps/admin/quarantine/index.js +4 -4
- package/dist/apps/admin/retention-policy/index.js +3 -3
- package/dist/apps/admin/signing-certificates/index.js +3 -3
- package/dist/apps/admin/transport-rules/[uid].js +4 -4
- package/dist/apps/admin/transport-rules/index.js +3 -3
- package/dist/apps/admin/transport-rules/new/index.js +3 -3
- package/dist/apps/escrow/matters/[uid].js +8 -3
- package/dist/apps/escrow/matters/new/index.js +4 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
- package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
- package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
- package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
- package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
- package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +2 -2
- package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +2 -2
- package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
- package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
- package/dist/apps/shared/components/admin/elevation.js +16 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
- package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
- package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
- package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +103 -18
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
- package/dist/apps/shared/components/admin/signOut.js +3 -0
- package/dist/apps/shared/components/admin/usePagedList.js +2 -2
- package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
- package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
- package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
- package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
- package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
- package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
- package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
- package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
- package/dist/apps/shared/components/calendar/EventModal.js +10 -2
- package/dist/apps/shared/components/calendar/FindATime.js +4 -3
- package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
- package/dist/apps/shared/components/calendar/ListView.js +92 -27
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
- package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
- package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
- package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
- package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
- package/dist/apps/shared/components/calendar/timePicker.js +5 -2
- package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
- package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
- package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
- package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
- package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
- package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
- package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
- package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
- package/dist/apps/shared/components/layout/AppShell.js +9 -0
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
- package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
- package/dist/apps/shared/components/mail/ConversationList.js +49 -6
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
- package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
- package/dist/apps/shared/components/mail/MenuButton.js +2 -211
- package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
- package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
- package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
- package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
- package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
- package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
- package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
- package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
- package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
- package/dist/apps/shared/components/tasks/dueDate.js +26 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
- package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
- package/dist/apps/shared/mail/decryptedMessages.js +46 -2
- package/dist/apps/shared/mail/listSnapshots.js +2 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
- package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
- package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
- package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
- package/dist/apps/shared/notifications/apiErrors.js +2 -0
- package/dist/apps/shared/notifications/preferences.d.ts +1 -1
- package/dist/apps/shared/notifications/preferences.js +1 -1
- package/dist/apps/shared/notifications/store.js +3 -1
- package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
- package/dist/apps/shared/plugins/pluginNav.js +4 -2
- package/dist/apps/www/calendar/index.js +24 -12
- package/dist/apps/www/contacts/[uid].js +6 -2
- package/dist/apps/www/contacts/index.js +62 -11
- package/dist/apps/www/index.js +85 -30
- package/dist/apps/www/messages/[uid].js +8 -3
- package/dist/apps/www/settings/auto-reply/index.js +13 -1
- package/dist/apps/www/settings/blocked-senders/index.js +4 -0
- package/dist/apps/www/settings/encryption/index.js +29 -5
- package/dist/apps/www/settings/privacy/index.js +3 -2
- package/dist/apps/www/settings/profile/index.js +4 -1
- package/dist/apps/www/settings/sharing/index.js +10 -1
- package/dist/apps/www/settings/signatures/[uid].js +6 -4
- package/dist/apps/www/tasks/index.js +94 -32
- package/dist/lib/admin/auditLogApi.js +2 -2
- package/dist/lib/admin/distributionListsApi.js +2 -2
- package/dist/lib/admin/domainsApi.js +2 -2
- package/dist/lib/admin/escrowAuditLogApi.js +2 -2
- package/dist/lib/admin/escrowScopesApi.js +2 -2
- package/dist/lib/admin/pluginsApi.d.ts +31 -2
- package/dist/lib/admin/pluginsApi.js +23 -1
- package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
- package/dist/lib/admin/retentionPolicyApi.js +3 -0
- package/dist/lib/admin/transportRulesApi.js +2 -2
- package/dist/lib/appearance/preferencesApi.d.ts +1 -1
- package/dist/lib/appearance/preferencesApi.js +3 -14
- package/dist/lib/auth/session.d.ts +51 -12
- package/dist/lib/auth/session.js +214 -38
- package/dist/lib/branding/brandingApi.d.ts +3 -3
- package/dist/lib/branding/brandingApi.js +9 -21
- package/dist/lib/calendar/calendarApi.js +3 -1
- package/dist/lib/calendar/eventSearch.js +43 -6
- package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
- package/dist/lib/calendar/freeBusyApi.js +13 -5
- package/dist/lib/calendar/recurrence.js +51 -3
- package/dist/lib/calendar/useDayKey.d.ts +7 -0
- package/dist/lib/calendar/useDayKey.js +43 -0
- package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
- package/dist/lib/components/avatar/ContactAvatar.js +37 -8
- package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
- package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
- package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
- package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
- package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
- package/dist/lib/components/forms/ImageEditBadge.js +62 -0
- package/dist/lib/components/menus/MenuButton.d.ts +78 -0
- package/dist/lib/components/menus/MenuButton.js +219 -0
- package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
- package/dist/lib/components/navigation/BottomTabBar.js +22 -3
- package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
- package/dist/lib/components/overlays/PopoverPortal.js +3 -2
- package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
- package/dist/lib/contacts/contactsApi.d.ts +5 -2
- package/dist/lib/contacts/contactsApi.js +16 -19
- package/dist/lib/contacts/gravatar.d.ts +14 -3
- package/dist/lib/contacts/gravatar.js +42 -5
- package/dist/lib/contacts/preparePhoto.d.ts +24 -0
- package/dist/lib/contacts/preparePhoto.js +250 -0
- package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
- package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
- package/dist/lib/contacts/vcard.d.ts +3 -0
- package/dist/lib/contacts/vcard.js +59 -7
- package/dist/lib/crypto/escrowKeys.d.ts +8 -0
- package/dist/lib/crypto/escrowKeys.js +20 -1
- package/dist/lib/crypto/messageSecurity.d.ts +7 -4
- package/dist/lib/crypto/messageSecurity.js +23 -5
- package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
- package/dist/lib/crypto/passwordUnlock.js +13 -2
- package/dist/lib/crypto/smime.js +12 -2
- package/dist/lib/mail/compose/composeQuoting.js +3 -2
- package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
- package/dist/lib/mail/compose/quotedImages.js +35 -9
- package/dist/lib/mail/labelsApi.js +2 -2
- package/dist/lib/mail/mailApi.d.ts +8 -5
- package/dist/lib/mail/mailApi.js +73 -39
- package/dist/lib/mail/mailDetailHooks.js +11 -3
- package/dist/lib/mail/mailFilterRulesApi.js +2 -2
- package/dist/lib/mail/mailSignaturesApi.js +2 -2
- package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
- package/dist/lib/mail/mailboxImportApi.js +3 -15
- package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
- package/dist/lib/mail/messageBodySanitizer.js +44 -1
- package/dist/lib/search/queryGrammar.js +12 -4
- package/dist/lib/search/searchTier3.d.ts +6 -1
- package/dist/lib/search/searchTier3.js +54 -25
- package/dist/lib/tasks/tasksApi.js +3 -3
- package/dist/lib/util/api.d.ts +26 -4
- package/dist/lib/util/api.js +73 -16
- package/dist/lib/util/apiQuery.d.ts +11 -0
- package/dist/lib/util/apiQuery.js +11 -0
- package/dist/lib/util/settleWithLimit.d.ts +5 -0
- package/dist/lib/util/settleWithLimit.js +25 -0
- package/lib/admin/auditLogApi.ts +2 -2
- package/lib/admin/distributionListsApi.ts +2 -2
- package/lib/admin/domainsApi.ts +2 -2
- package/lib/admin/escrowAuditLogApi.ts +2 -2
- package/lib/admin/escrowScopesApi.ts +2 -2
- package/lib/admin/pluginsApi.ts +45 -3
- package/lib/admin/retentionPolicyApi.ts +4 -0
- package/lib/admin/transportRulesApi.ts +2 -2
- package/lib/appearance/preferencesApi.ts +3 -14
- package/lib/auth/session.ts +239 -39
- package/lib/branding/brandingApi.ts +9 -21
- package/lib/calendar/calendarApi.ts +3 -1
- package/lib/calendar/eventSearch.ts +46 -6
- package/lib/calendar/freeBusyApi.ts +14 -6
- package/lib/calendar/recurrence.ts +59 -8
- package/lib/calendar/useDayKey.ts +45 -0
- package/lib/components/avatar/ContactAvatar.tsx +46 -8
- package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
- package/lib/components/forms/CameraCaptureModal.tsx +133 -0
- package/lib/components/forms/ImageEditBadge.tsx +121 -0
- package/lib/components/menus/MenuButton.tsx +408 -0
- package/lib/components/navigation/BottomTabBar.tsx +30 -8
- package/lib/components/overlays/PopoverPortal.tsx +3 -2
- package/lib/components/pickers/TimeZonePicker.tsx +17 -1
- package/lib/contacts/contactsApi.ts +18 -19
- package/lib/contacts/gravatar.ts +47 -5
- package/lib/contacts/preparePhoto.ts +273 -0
- package/lib/contacts/useContactPhotoSrc.ts +177 -0
- package/lib/contacts/vcard.ts +60 -7
- package/lib/crypto/escrowKeys.ts +21 -1
- package/lib/crypto/messageSecurity.ts +33 -9
- package/lib/crypto/passwordUnlock.ts +14 -2
- package/lib/crypto/smime.ts +13 -2
- package/lib/mail/compose/composeQuoting.ts +3 -2
- package/lib/mail/compose/quotedImages.ts +39 -9
- package/lib/mail/labelsApi.ts +2 -2
- package/lib/mail/mailApi.ts +96 -39
- package/lib/mail/mailDetailHooks.ts +11 -3
- package/lib/mail/mailFilterRulesApi.ts +2 -2
- package/lib/mail/mailSignaturesApi.ts +2 -2
- package/lib/mail/mailboxImportApi.ts +9 -15
- package/lib/mail/messageBodySanitizer.ts +47 -1
- package/lib/search/queryGrammar.ts +13 -4
- package/lib/search/searchTier3.ts +65 -32
- package/lib/tasks/tasksApi.ts +3 -3
- package/lib/util/api.ts +99 -16
- package/lib/util/apiQuery.ts +13 -0
- package/lib/util/settleWithLimit.ts +25 -0
- package/package.json +1 -1
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
import React, { FormEvent, useCallback, useEffect, useId, useRef, useState } from "react";
|
|
6
|
-
import { ApiRequestError } from "../../../../../lib/util/api.js";
|
|
7
6
|
import {
|
|
8
7
|
addPlugin,
|
|
9
8
|
expectedPlanOf,
|
|
10
9
|
getPluginStatus,
|
|
11
10
|
getPluginUpdates,
|
|
11
|
+
isSecretSetting,
|
|
12
|
+
isSecretSettingKey,
|
|
12
13
|
listPluginNamespaces,
|
|
13
14
|
listPlugins,
|
|
14
15
|
lookupPluginPackage,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
PluginPurgeInfo,
|
|
22
23
|
PluginPurgeState,
|
|
23
24
|
PluginRegistryLookup,
|
|
25
|
+
PluginSavedSetting,
|
|
24
26
|
PluginSearchResult,
|
|
25
27
|
PluginSettingDefinition,
|
|
26
28
|
PluginSettingValue,
|
|
@@ -31,12 +33,14 @@ import {
|
|
|
31
33
|
retryPluginPurge,
|
|
32
34
|
searchPlugins,
|
|
33
35
|
updatePlugin,
|
|
36
|
+
uploadPlugin,
|
|
34
37
|
} from "../../../../../lib/admin/pluginsApi.js";
|
|
35
|
-
import
|
|
38
|
+
import { ApiRequestError } from "../../../../../lib/util/api.js";
|
|
39
|
+
import Alert from "../ActionAlert.js";
|
|
36
40
|
import Button from "../../../../../lib/components/buttons/Button.js";
|
|
37
41
|
import Modal from "../../../../../lib/components/overlays/Modal.js";
|
|
38
42
|
import { notify } from "../../../notifications/store.js";
|
|
39
|
-
import { isElevationRequired } from "../elevation.js";
|
|
43
|
+
import { actionErrorMessage, isElevationMessage, isElevationRequired } from "../elevation.js";
|
|
40
44
|
import { getAllowPrerelease, setAllowPrerelease } from "./pluginPreferences.js";
|
|
41
45
|
|
|
42
46
|
const INPUT_CLASS =
|
|
@@ -52,7 +56,7 @@ const AFTER_CHANGE_POLL_MS = 2 * 60 * 1000;
|
|
|
52
56
|
const MAX_STATUS_RETRY_MS = 60 * 1000;
|
|
53
57
|
|
|
54
58
|
function errorMessage(err: unknown, fallback: string): string {
|
|
55
|
-
return err
|
|
59
|
+
return actionErrorMessage(err, fallback);
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
/** Runs `action`, resolving why it failed, or `null`. */
|
|
@@ -82,6 +86,21 @@ function purgeFailure(purge: PluginPurgeInfo): string {
|
|
|
82
86
|
/** A day, in the administrator's own format. */
|
|
83
87
|
const formatDate = (iso: string): string => new Date(iso).toLocaleDateString(undefined, { dateStyle: "medium" });
|
|
84
88
|
|
|
89
|
+
/** The file names an uploaded plugin may have: what `npm pack` produces (`<name>-<version>.tgz`), or the same gzipped. */
|
|
90
|
+
const PLUGIN_PACK_NAME = /\.(tgz|tar\.gz|gz)$/i;
|
|
91
|
+
|
|
92
|
+
/** What the server answers (409) an upload of a plugin that is already installed, which the administrator may replace. */
|
|
93
|
+
const ALREADY_INSTALLED = /already installed/i;
|
|
94
|
+
|
|
95
|
+
/** An upload the administrator is being asked to let replace the plugin of the same name. */
|
|
96
|
+
interface PendingReplace {
|
|
97
|
+
file: File;
|
|
98
|
+
/** What the replacement would do, as a question. */
|
|
99
|
+
question: string;
|
|
100
|
+
/** Why the server refused, in its words. */
|
|
101
|
+
reason: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
85
104
|
/** A change waiting for the administrator to confirm the other plugins it also installs or enables. */
|
|
86
105
|
interface PendingChange {
|
|
87
106
|
displayName: string;
|
|
@@ -212,6 +231,8 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
212
231
|
const allowPrereleaseRef = useRef(allowPrerelease);
|
|
213
232
|
/** Identifies the latest update check, so a slower earlier one can't replace what it found. */
|
|
214
233
|
const updatesRequest = useRef(0);
|
|
234
|
+
// Set while a bulk action runs, which looks for updates once at its end rather than after each plugin it changes.
|
|
235
|
+
const bulkRunning = useRef(false);
|
|
215
236
|
/** The plugins ticked for a bulk action. */
|
|
216
237
|
const [selected, setSelected] = useState<ReadonlySet<string>>(new Set());
|
|
217
238
|
/** Set while a bulk action runs. */
|
|
@@ -221,6 +242,11 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
221
242
|
const [confirmingBulkUninstall, setConfirmingBulkUninstall] = useState<Plugin[] | null>(null);
|
|
222
243
|
/** How many changes (or the lookups that come before them) are waiting on the server. */
|
|
223
244
|
const [working, setWorking] = useState(0);
|
|
245
|
+
/** Set while an uploaded plugin is being sent to the server. */
|
|
246
|
+
const [uploading, setUploading] = useState(false);
|
|
247
|
+
/** An upload that would replace an installed plugin, waiting for the administrator to confirm it. */
|
|
248
|
+
const [replacing, setReplacing] = useState<PendingReplace | null>(null);
|
|
249
|
+
const fileInput = useRef<HTMLInputElement>(null);
|
|
224
250
|
const statusRequest = useRef<Promise<void> | null>(null);
|
|
225
251
|
/** The state each data deletion was last seen in, so a notification is raised when one that was under way ends. */
|
|
226
252
|
const seenPurges = useRef<Map<string, PluginPurgeState>>(new Map());
|
|
@@ -378,7 +404,9 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
378
404
|
return [...next].sort((a, b) => a.name.localeCompare(b.name));
|
|
379
405
|
});
|
|
380
406
|
watchRollout();
|
|
381
|
-
|
|
407
|
+
if (!bulkRunning.current) {
|
|
408
|
+
void refreshUpdates();
|
|
409
|
+
}
|
|
382
410
|
}
|
|
383
411
|
|
|
384
412
|
/** Runs an action on a plugin's row (an installed plugin's, or an uninstalled one's), which is busy meanwhile, and
|
|
@@ -511,6 +539,54 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
511
539
|
return onRow(plugin, () => changeVersion(plugin, packageVersion));
|
|
512
540
|
}
|
|
513
541
|
|
|
542
|
+
/** Sends `file` to the server as a plugin to install - replacing the installed plugin of that name only when `replace` is set. A plugin that is
|
|
543
|
+
* already installed isn't an error but a question, asked of the administrator before it is replaced. */
|
|
544
|
+
async function runUpload(file: File, replace: boolean) {
|
|
545
|
+
setUploading(true);
|
|
546
|
+
setError(null);
|
|
547
|
+
setRetryEnableUid(null);
|
|
548
|
+
try {
|
|
549
|
+
const uploaded: Plugin = await track(uploadPlugin(file, { replace }));
|
|
550
|
+
applied(uploaded);
|
|
551
|
+
notify({
|
|
552
|
+
kind: "success",
|
|
553
|
+
title: `${uploaded.name}@${uploaded.packageVersion} uploaded`,
|
|
554
|
+
message: "The servers restart one at a time to load it.",
|
|
555
|
+
});
|
|
556
|
+
} catch (err) {
|
|
557
|
+
if (!replace && err instanceof ApiRequestError && err.status === 409 && ALREADY_INSTALLED.test(err.message)) {
|
|
558
|
+
const installed: Plugin | undefined = plugins.find((plugin) => err.message.includes(plugin.name));
|
|
559
|
+
setReplacing({
|
|
560
|
+
file,
|
|
561
|
+
question: installed
|
|
562
|
+
? `Replace ${installed.name} ${installed.packageVersion} with the uploaded pack?`
|
|
563
|
+
: "Replace the installed plugin with the uploaded pack?",
|
|
564
|
+
reason: err.message,
|
|
565
|
+
});
|
|
566
|
+
} else if (err instanceof ApiRequestError && err.status === 413) {
|
|
567
|
+
setError("That file is too large for the server to accept.");
|
|
568
|
+
} else {
|
|
569
|
+
setError(errorMessage(err, "Could not upload the plugin."));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
setUploading(false);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** Takes the file the administrator chose, unless it can't be what `npm pack` produces. */
|
|
576
|
+
function onFileChosen(e: React.ChangeEvent<HTMLInputElement>) {
|
|
577
|
+
const file: File | undefined = e.target.files?.[0];
|
|
578
|
+
// So choosing the same file again (after fixing what was wrong) still counts as a change.
|
|
579
|
+
e.target.value = "";
|
|
580
|
+
if (!file) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (!PLUGIN_PACK_NAME.test(file.name)) {
|
|
584
|
+
setError(`${file.name} isn't a plugin pack. Choose the .tgz file that npm pack produces.`);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
void runUpload(file, false);
|
|
588
|
+
}
|
|
589
|
+
|
|
514
590
|
/** Installs an uninstalled plugin again, at its latest version - as adding it by name would. */
|
|
515
591
|
function reinstall(purge: PluginPurgeInfo) {
|
|
516
592
|
const displayName: string = purgeName(purge);
|
|
@@ -579,6 +655,7 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
579
655
|
setError(null);
|
|
580
656
|
setRetryEnableUid(null);
|
|
581
657
|
setBulkFailure(null);
|
|
658
|
+
bulkRunning.current = true;
|
|
582
659
|
await track(
|
|
583
660
|
(async () => {
|
|
584
661
|
for (const [index, plugin] of ordered.entries()) {
|
|
@@ -594,7 +671,7 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
594
671
|
}
|
|
595
672
|
failed.add(plugin.uid);
|
|
596
673
|
reasons.push(`${plugin.manifest.displayName}: ${problem}`);
|
|
597
|
-
if (problem
|
|
674
|
+
if (isElevationMessage(problem)) {
|
|
598
675
|
for (const untried of ordered.slice(index + 1)) {
|
|
599
676
|
failed.add(untried.uid);
|
|
600
677
|
reasons.push(`${untried.manifest.displayName}: Not attempted.`);
|
|
@@ -604,12 +681,16 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
604
681
|
}
|
|
605
682
|
})(),
|
|
606
683
|
);
|
|
684
|
+
bulkRunning.current = false;
|
|
607
685
|
setBulk(null);
|
|
608
686
|
setSelected(failed);
|
|
609
687
|
const succeeded: number = ordered.length - failed.size;
|
|
610
688
|
if (kind === "uninstall" && succeeded > 0) {
|
|
611
689
|
watchRollout();
|
|
612
690
|
}
|
|
691
|
+
if (kind !== "uninstall" && succeeded > 0) {
|
|
692
|
+
void refreshUpdates();
|
|
693
|
+
}
|
|
613
694
|
if (succeeded > 0) {
|
|
614
695
|
notify({
|
|
615
696
|
kind: "success",
|
|
@@ -671,12 +752,34 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
671
752
|
const displayNameOf = (name: string): string => plugins.find((plugin) => plugin.name === name)?.manifest.displayName ?? name;
|
|
672
753
|
|
|
673
754
|
const SectionHeading = embedded ? "h3" : "h2";
|
|
755
|
+
const bulkBusy: boolean = bulk !== null;
|
|
674
756
|
const addButton = (
|
|
675
|
-
<
|
|
676
|
-
|
|
677
|
-
|
|
757
|
+
<div className="flex flex-wrap gap-2 shrink-0">
|
|
758
|
+
<input
|
|
759
|
+
ref={fileInput}
|
|
760
|
+
type="file"
|
|
761
|
+
className="hidden"
|
|
762
|
+
accept=".tgz,.tar.gz,application/gzip,application/x-gzip"
|
|
763
|
+
aria-label="Plugin pack file"
|
|
764
|
+
tabIndex={-1}
|
|
765
|
+
onChange={onFileChosen}
|
|
766
|
+
/>
|
|
767
|
+
<Button
|
|
768
|
+
type="button"
|
|
769
|
+
variant="secondary"
|
|
770
|
+
className="!w-auto"
|
|
771
|
+
title="Install a plugin from the .tgz file that npm pack produces"
|
|
772
|
+
loading={uploading}
|
|
773
|
+
disabled={uploading || bulkBusy}
|
|
774
|
+
onClick={() => fileInput.current?.click()}
|
|
775
|
+
>
|
|
776
|
+
{uploading ? "Uploading\u2026" : "Upload"}
|
|
777
|
+
</Button>
|
|
778
|
+
<Button type="button" variant="secondary" className="!w-auto" onClick={() => setAdding(true)}>
|
|
779
|
+
Add by name
|
|
780
|
+
</Button>
|
|
781
|
+
</div>
|
|
678
782
|
);
|
|
679
|
-
const bulkBusy: boolean = bulk !== null;
|
|
680
783
|
|
|
681
784
|
return (
|
|
682
785
|
<>
|
|
@@ -730,24 +833,18 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
730
833
|
</div>
|
|
731
834
|
</Alert>
|
|
732
835
|
)}
|
|
733
|
-
{
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
Try again
|
|
746
|
-
</Button>
|
|
747
|
-
</>
|
|
748
|
-
)}
|
|
749
|
-
</Alert>
|
|
750
|
-
)}
|
|
836
|
+
{/* A message on its own is a plain string child, which is what lets the alert offer to confirm the identity again. */}
|
|
837
|
+
{error &&
|
|
838
|
+
(retryEnable ? (
|
|
839
|
+
<Alert>
|
|
840
|
+
{error}{" "}
|
|
841
|
+
<Button type="button" variant="text" className="!w-auto !p-0" onClick={() => void toggle(retryEnable)}>
|
|
842
|
+
Try again
|
|
843
|
+
</Button>
|
|
844
|
+
</Alert>
|
|
845
|
+
) : (
|
|
846
|
+
<Alert>{error}</Alert>
|
|
847
|
+
))}
|
|
751
848
|
<RolloutBanner status={status} />
|
|
752
849
|
{statusStale && status && (
|
|
753
850
|
<p className="mb-4 text-xs text-text-muted">Couldn't refresh server status. Showing the last status reported.</p>
|
|
@@ -860,7 +957,9 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
860
957
|
<tbody role="rowgroup" className="block lg:table-row-group">
|
|
861
958
|
{plugins.map((plugin) => {
|
|
862
959
|
const update: PluginUpdateInfo | undefined = updates.get(plugin.uid);
|
|
863
|
-
const
|
|
960
|
+
const uploaded: boolean = plugin.source === "upload";
|
|
961
|
+
// An uploaded pack has no registry version to be newer than.
|
|
962
|
+
const latest: string | undefined = !uploaded && update?.updateAvailable ? update.latestVersion : undefined;
|
|
864
963
|
const busy: boolean = busyUids.has(plugin.uid) || confirming?.uid === plugin.uid || bulkBusy;
|
|
865
964
|
const requires: string[] = Object.keys(plugin.manifest.requires ?? {}).map(displayNameOf);
|
|
866
965
|
const requiredBy: string[] = plugins
|
|
@@ -884,6 +983,13 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
884
983
|
{plugin.manifest.description && (
|
|
885
984
|
<div className="text-xs text-text-muted mt-1 max-w-md">{plugin.manifest.description}</div>
|
|
886
985
|
)}
|
|
986
|
+
{uploaded && (
|
|
987
|
+
<div className="text-xs text-text-muted mt-1 break-words">
|
|
988
|
+
<span className={`${BADGE_CLASS} bg-surface-alt text-text-muted`}>Uploaded</span>{" "}
|
|
989
|
+
{plugin.uploadFilename}
|
|
990
|
+
{plugin.uploadedAt ? `, ${formatDate(plugin.uploadedAt)}` : ""}
|
|
991
|
+
</div>
|
|
992
|
+
)}
|
|
887
993
|
{requires.length > 0 && <div className="text-xs text-text-muted mt-1">Requires: {requires.join(", ")}</div>}
|
|
888
994
|
{requiredBy.length > 0 && (
|
|
889
995
|
<div className="text-xs text-text-muted mt-1">Required by: {requiredBy.join(", ")}</div>
|
|
@@ -931,7 +1037,7 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
931
1037
|
</div>
|
|
932
1038
|
<div className="flex flex-wrap gap-x-4 lg:justify-end">
|
|
933
1039
|
<Button variant="text" type="button" onClick={() => setUpgrading(plugin)}>
|
|
934
|
-
Change version
|
|
1040
|
+
{uploaded ? "Install the registry version" : "Change version"}
|
|
935
1041
|
</Button>
|
|
936
1042
|
<Button variant="text" type="button" onClick={() => setRemoving(plugin)}>
|
|
937
1043
|
Uninstall
|
|
@@ -991,6 +1097,28 @@ export default function PluginsManager({ embedded = false }: PluginsManagerProps
|
|
|
991
1097
|
}}
|
|
992
1098
|
/>
|
|
993
1099
|
)}
|
|
1100
|
+
{replacing && (
|
|
1101
|
+
<Modal open onClose={() => setReplacing(null)} title="Replace plugin?">
|
|
1102
|
+
<p className="text-sm mb-2">{replacing.question}</p>
|
|
1103
|
+
<p className="text-sm text-text-muted mb-4 break-words">{replacing.reason}</p>
|
|
1104
|
+
<div className="flex gap-2 justify-end">
|
|
1105
|
+
<Button type="button" variant="secondary" className="!w-auto" onClick={() => setReplacing(null)}>
|
|
1106
|
+
Cancel
|
|
1107
|
+
</Button>
|
|
1108
|
+
<Button
|
|
1109
|
+
type="button"
|
|
1110
|
+
className="!w-auto"
|
|
1111
|
+
onClick={() => {
|
|
1112
|
+
const { file } = replacing;
|
|
1113
|
+
setReplacing(null);
|
|
1114
|
+
void runUpload(file, true);
|
|
1115
|
+
}}
|
|
1116
|
+
>
|
|
1117
|
+
Replace
|
|
1118
|
+
</Button>
|
|
1119
|
+
</div>
|
|
1120
|
+
</Modal>
|
|
1121
|
+
)}
|
|
994
1122
|
{confirming && (
|
|
995
1123
|
<ConfirmDependenciesModal
|
|
996
1124
|
change={confirming}
|
|
@@ -1559,15 +1687,20 @@ function ChangeVersionModal({
|
|
|
1559
1687
|
const [selected, setSelected] = useState(plugin.packageVersion);
|
|
1560
1688
|
const [error, setError] = useState<string | null>(null);
|
|
1561
1689
|
const [busy, setBusy] = useState(false);
|
|
1690
|
+
// An uploaded pack isn't a registry version, so any registry version is a change from it - the newest is offered first.
|
|
1691
|
+
const uploaded: boolean = plugin.source === "upload";
|
|
1562
1692
|
|
|
1563
1693
|
useEffect(() => {
|
|
1564
1694
|
lookupPluginPackage(plugin.name, undefined, { prerelease })
|
|
1565
1695
|
.then((found) => {
|
|
1566
1696
|
setVersions(found.package.versions);
|
|
1567
1697
|
setLatest(found.package.latest);
|
|
1698
|
+
if (uploaded && found.package.latest) {
|
|
1699
|
+
setSelected(found.package.latest);
|
|
1700
|
+
}
|
|
1568
1701
|
})
|
|
1569
1702
|
.catch((err) => setError(errorMessage(err, "Could not load the available versions.")));
|
|
1570
|
-
}, [plugin.name, prerelease]);
|
|
1703
|
+
}, [plugin.name, prerelease, uploaded]);
|
|
1571
1704
|
|
|
1572
1705
|
async function save() {
|
|
1573
1706
|
setBusy(true);
|
|
@@ -1580,12 +1713,17 @@ function ChangeVersionModal({
|
|
|
1580
1713
|
}
|
|
1581
1714
|
|
|
1582
1715
|
return (
|
|
1583
|
-
<Modal open onClose={onClose} title={`${plugin.manifest.displayName} version`}>
|
|
1716
|
+
<Modal open onClose={onClose} title={uploaded ? `Install the registry version of ${plugin.manifest.displayName}` : `${plugin.manifest.displayName} version`}>
|
|
1584
1717
|
{error && <Alert>{error}</Alert>}
|
|
1585
1718
|
{versions === null ? (
|
|
1586
1719
|
!error && <p className="text-sm text-text-muted">Loading…</p>
|
|
1587
1720
|
) : (
|
|
1588
1721
|
<div className="flex flex-col gap-3">
|
|
1722
|
+
{uploaded && (
|
|
1723
|
+
<p className="text-sm text-text-muted">
|
|
1724
|
+
{plugin.manifest.displayName} {plugin.packageVersion} was uploaded as {plugin.uploadFilename}. This replaces it with the version from the registry.
|
|
1725
|
+
</p>
|
|
1726
|
+
)}
|
|
1589
1727
|
<label className="flex flex-col gap-1.5 text-sm">
|
|
1590
1728
|
<span className="font-semibold">Version</span>
|
|
1591
1729
|
<select aria-label="Version" className={INPUT_CLASS} value={selected} onChange={(e) => setSelected(e.target.value)}>
|
|
@@ -1593,7 +1731,7 @@ function ChangeVersionModal({
|
|
|
1593
1731
|
<option key={version} value={version}>
|
|
1594
1732
|
{version}
|
|
1595
1733
|
{version === latest ? " (latest)" : ""}
|
|
1596
|
-
{version === plugin.packageVersion ? " (installed)" : ""}
|
|
1734
|
+
{!uploaded && version === plugin.packageVersion ? " (installed)" : ""}
|
|
1597
1735
|
</option>
|
|
1598
1736
|
))}
|
|
1599
1737
|
</select>
|
|
@@ -1606,10 +1744,10 @@ function ChangeVersionModal({
|
|
|
1606
1744
|
type="button"
|
|
1607
1745
|
className="!w-auto"
|
|
1608
1746
|
loading={busy}
|
|
1609
|
-
disabled={busy || selected === plugin.packageVersion}
|
|
1747
|
+
disabled={busy || (!uploaded && selected === plugin.packageVersion)}
|
|
1610
1748
|
onClick={() => void save()}
|
|
1611
1749
|
>
|
|
1612
|
-
Save
|
|
1750
|
+
{uploaded ? "Install" : "Save"}
|
|
1613
1751
|
</Button>
|
|
1614
1752
|
</div>
|
|
1615
1753
|
</div>
|
|
@@ -1675,7 +1813,7 @@ function ConfirmDependenciesModal({
|
|
|
1675
1813
|
}
|
|
1676
1814
|
|
|
1677
1815
|
/** Whether a required select has no value to fall back on, so the form has to pick (and save) one. */
|
|
1678
|
-
function needsSelection(definition: PluginSettingDefinition, saved:
|
|
1816
|
+
function needsSelection(definition: PluginSettingDefinition, saved: PluginSavedSetting | undefined): boolean {
|
|
1679
1817
|
return (
|
|
1680
1818
|
definition.type === "select" &&
|
|
1681
1819
|
!!definition.required &&
|
|
@@ -1693,7 +1831,7 @@ const HOST_PLACEHOLDER = "<host>";
|
|
|
1693
1831
|
* saved yet - nothing, an empty value, or the placeholder itself, which an older server stores when it installs the
|
|
1694
1832
|
* plugin. It's the default with this console's host filled in, and is only stored once the form is saved.
|
|
1695
1833
|
*/
|
|
1696
|
-
function suggestedHost(definition: PluginSettingDefinition, saved:
|
|
1834
|
+
function suggestedHost(definition: PluginSettingDefinition, saved: PluginSavedSetting | undefined): string | undefined {
|
|
1697
1835
|
if (typeof definition.default !== "string" || !definition.default.includes(HOST_PLACEHOLDER) || typeof window === "undefined") {
|
|
1698
1836
|
return undefined;
|
|
1699
1837
|
}
|
|
@@ -1710,12 +1848,12 @@ function configuredValue(definition: PluginSettingDefinition, configured: Plugin
|
|
|
1710
1848
|
}
|
|
1711
1849
|
|
|
1712
1850
|
/** Whether a saved value is absent - nothing, or the empty value a plugin's own "" default is saved as. */
|
|
1713
|
-
function isUnsaved(saved:
|
|
1851
|
+
function isUnsaved(saved: PluginSavedSetting | undefined): boolean {
|
|
1714
1852
|
return saved === undefined || saved === "";
|
|
1715
1853
|
}
|
|
1716
1854
|
|
|
1717
1855
|
/** The suggestion for the host, unless the deployment configures the setting, which is offered instead. */
|
|
1718
|
-
function hostSuggestion(definition: PluginSettingDefinition, saved:
|
|
1856
|
+
function hostSuggestion(definition: PluginSettingDefinition, saved: PluginSavedSetting | undefined, configured: PluginConfiguredSetting | undefined): string | undefined {
|
|
1719
1857
|
return configured ? undefined : suggestedHost(definition, saved);
|
|
1720
1858
|
}
|
|
1721
1859
|
|
|
@@ -1724,9 +1862,13 @@ function hostSuggestion(definition: PluginSettingDefinition, saved: PluginSettin
|
|
|
1724
1862
|
* option. Kept as a string for text inputs. */
|
|
1725
1863
|
function initialValue(
|
|
1726
1864
|
definition: PluginSettingDefinition,
|
|
1727
|
-
saved:
|
|
1865
|
+
saved: PluginSavedSetting | undefined,
|
|
1728
1866
|
configured?: PluginConfiguredSetting,
|
|
1729
1867
|
): PluginSettingValue | "" {
|
|
1868
|
+
if (isSecretSetting(saved)) {
|
|
1869
|
+
// Write-only: the saved secret never reaches the browser, so the field starts empty. Left empty, the placeholder is sent back and the secret stays.
|
|
1870
|
+
return "";
|
|
1871
|
+
}
|
|
1730
1872
|
if (configured && isUnsaved(saved)) {
|
|
1731
1873
|
return configuredValue(definition, configured);
|
|
1732
1874
|
}
|
|
@@ -1768,12 +1910,12 @@ function SettingsModal({ plugin, onClose, onSaved }: { plugin: Plugin; onClose:
|
|
|
1768
1910
|
// A required select with nothing to fall back on, and a setting offering this console's host, are sent as the
|
|
1769
1911
|
// form shows them. What the deployment configures is never saved as a copy, which would outrank it when it changes
|
|
1770
1912
|
// - a field that shows it is sent as nothing.
|
|
1771
|
-
const settings: Record<string,
|
|
1913
|
+
const settings: Record<string, PluginSavedSetting | null> = {};
|
|
1772
1914
|
let changed = false;
|
|
1773
1915
|
for (const definition of definitions) {
|
|
1774
1916
|
const key: string = definition.key;
|
|
1775
1917
|
const configured: PluginConfiguredSetting | undefined = plugin.configured?.[key];
|
|
1776
|
-
const saved:
|
|
1918
|
+
const saved: PluginSavedSetting | undefined = plugin.settings[key];
|
|
1777
1919
|
const value = values[key];
|
|
1778
1920
|
if (resetting && value === initialValue(definition, undefined, configured)) {
|
|
1779
1921
|
// What a fresh install saves: only a default naming this console's host has to be, for the plugin to work.
|
|
@@ -1820,6 +1962,7 @@ function SettingsModal({ plugin, onClose, onSaved }: { plugin: Plugin; onClose:
|
|
|
1820
1962
|
definition={definition}
|
|
1821
1963
|
value={values[definition.key]}
|
|
1822
1964
|
configured={plugin.configured?.[definition.key]}
|
|
1965
|
+
secretSaved={!resetting && isSecretSetting(plugin.settings[definition.key])}
|
|
1823
1966
|
onChange={(value) => setValues((prev) => ({ ...prev, [definition.key]: value }))}
|
|
1824
1967
|
/>
|
|
1825
1968
|
))}
|
|
@@ -1852,11 +1995,14 @@ function SettingField({
|
|
|
1852
1995
|
definition,
|
|
1853
1996
|
value,
|
|
1854
1997
|
configured,
|
|
1998
|
+
secretSaved,
|
|
1855
1999
|
onChange,
|
|
1856
2000
|
}: {
|
|
1857
2001
|
definition: PluginSettingDefinition;
|
|
1858
2002
|
value: PluginSettingValue | "";
|
|
1859
2003
|
configured?: PluginConfiguredSetting;
|
|
2004
|
+
/** A secret is saved: the server never sends it, so the field is write-only - typing replaces it, leaving it empty keeps it. */
|
|
2005
|
+
secretSaved: boolean;
|
|
1860
2006
|
onChange: (value: PluginSettingValue | "") => void;
|
|
1861
2007
|
}) {
|
|
1862
2008
|
// What applies without a saved value; one saved here replaces it, and Reset goes back to it.
|
|
@@ -1865,8 +2011,10 @@ function SettingField({
|
|
|
1865
2011
|
This deployment's configuration provides {configured.secret ? "a value" : "the value shown"} unless one is saved here.
|
|
1866
2012
|
</span>
|
|
1867
2013
|
);
|
|
1868
|
-
// A secret's value never reaches the browser, so the field only says that it is set
|
|
1869
|
-
|
|
2014
|
+
// A secret's value never reaches the browser, so the field only says that it is set - and never shows what is typed, or offers it to the
|
|
2015
|
+
// browser's form fill.
|
|
2016
|
+
const secretField: boolean = definition.type === "string" && (isSecretSettingKey(definition.key) || secretSaved || !!configured?.secret);
|
|
2017
|
+
const placeholder: string | undefined = secretSaved ? "Saved - type a new value to replace it" : configured?.secret ? "Set by this deployment" : undefined;
|
|
1870
2018
|
const help = (
|
|
1871
2019
|
<>
|
|
1872
2020
|
{definition.help && <span className="text-xs text-text-muted">{definition.help}</span>}
|
|
@@ -1901,10 +2049,11 @@ function SettingField({
|
|
|
1901
2049
|
<input
|
|
1902
2050
|
aria-label={definition.label}
|
|
1903
2051
|
className={INPUT_CLASS}
|
|
1904
|
-
type={definition.type === "number" ? "number" : "text"}
|
|
2052
|
+
type={definition.type === "number" ? "number" : secretField ? "password" : "text"}
|
|
2053
|
+
autoComplete={secretField ? "new-password" : undefined}
|
|
1905
2054
|
min={definition.min}
|
|
1906
2055
|
max={definition.max}
|
|
1907
|
-
required={definition.required && !configured}
|
|
2056
|
+
required={definition.required && !configured && !secretSaved}
|
|
1908
2057
|
placeholder={placeholder}
|
|
1909
2058
|
value={String(value)}
|
|
1910
2059
|
onChange={(e) => onChange(e.target.value)}
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
import React, { FormEvent, useEffect, useState } from "react";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
import { actionErrorMessage } from "../elevation.js";
|
|
7
|
+
import {
|
|
8
|
+
MIN_AUDIT_LOG_RETENTION_DAYS,
|
|
9
|
+
MIN_MESSAGE_RETENTION_DAYS,
|
|
10
|
+
RetentionPolicy,
|
|
11
|
+
RetentionPolicyUpdate,
|
|
12
|
+
updateRetentionPolicy,
|
|
13
|
+
} from "../../../../../lib/admin/retentionPolicyApi.js";
|
|
14
|
+
import Alert from "../ActionAlert.js";
|
|
9
15
|
import Button from "../../../../../lib/components/buttons/Button.js";
|
|
10
16
|
import Modal from "../../../../../lib/components/overlays/Modal.js";
|
|
11
17
|
|
|
@@ -49,14 +55,21 @@ export default function RetentionPolicyForm({
|
|
|
49
55
|
const [savedMessageDays, setSavedMessageDays] = useState(policy.messageRetentionDays);
|
|
50
56
|
const [savedAuditDays, setSavedAuditDays] = useState(policy.auditLogRetentionDays);
|
|
51
57
|
const [pendingPatch, setPendingPatch] = useState<RetentionPolicyUpdate | null>(null);
|
|
58
|
+
// A period that is still what is stored is neither sent nor held to the floor: it may have been stored before the floor was set.
|
|
59
|
+
const messageChanged = messageRetentionDays !== (savedMessageDays?.toString() ?? "");
|
|
60
|
+
const auditChanged = auditLogRetentionDays !== (savedAuditDays?.toString() ?? "");
|
|
52
61
|
|
|
53
62
|
function handleSubmit(e: FormEvent) {
|
|
54
63
|
e.preventDefault();
|
|
55
|
-
// A blank field is sent as `null`, which clears a configured age back to no automatic purge.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
// A blank field is sent as `null`, which clears a configured age back to no automatic purge. Only a field that was changed is sent: one left as
|
|
65
|
+
// it is would be refused by a floor that came in after it was stored, and so keep the other from being saved.
|
|
66
|
+
const patch: RetentionPolicyUpdate = {};
|
|
67
|
+
if (messageChanged) {
|
|
68
|
+
patch.messageRetentionDays = messageRetentionDays.trim() === "" ? null : Number(messageRetentionDays);
|
|
69
|
+
}
|
|
70
|
+
if (auditChanged) {
|
|
71
|
+
patch.auditLogRetentionDays = auditLogRetentionDays.trim() === "" ? null : Number(auditLogRetentionDays);
|
|
72
|
+
}
|
|
60
73
|
if (
|
|
61
74
|
shortensRetention(patch.messageRetentionDays, savedMessageDays) ||
|
|
62
75
|
shortensRetention(patch.auditLogRetentionDays, savedAuditDays)
|
|
@@ -80,7 +93,7 @@ export default function RetentionPolicyForm({
|
|
|
80
93
|
setSavedSnapshot(snapshot);
|
|
81
94
|
setSaved(true);
|
|
82
95
|
} catch (err) {
|
|
83
|
-
setError(err
|
|
96
|
+
setError(actionErrorMessage(err, "Could not save the retention policy."));
|
|
84
97
|
} finally {
|
|
85
98
|
setSaving(false);
|
|
86
99
|
}
|
|
@@ -109,14 +122,14 @@ export default function RetentionPolicyForm({
|
|
|
109
122
|
<input
|
|
110
123
|
aria-label="Message retention (days)"
|
|
111
124
|
type="number"
|
|
112
|
-
min={
|
|
125
|
+
min={messageChanged ? MIN_MESSAGE_RETENTION_DAYS : undefined}
|
|
113
126
|
className={INPUT_CLASS}
|
|
114
127
|
value={messageRetentionDays}
|
|
115
128
|
onChange={(e) => setMessageRetentionDays(e.target.value)}
|
|
116
129
|
placeholder="No automatic purge"
|
|
117
130
|
/>
|
|
118
131
|
<span className="text-xs text-text-muted">
|
|
119
|
-
Applies to any message, in any folder, regardless of age.
|
|
132
|
+
Applies to any message, in any folder, regardless of age. Cannot be set below {MIN_MESSAGE_RETENTION_DAYS} days.
|
|
120
133
|
</span>
|
|
121
134
|
</label>
|
|
122
135
|
<label className="flex flex-col gap-1.5 text-sm">
|
|
@@ -124,7 +137,7 @@ export default function RetentionPolicyForm({
|
|
|
124
137
|
<input
|
|
125
138
|
aria-label="Audit log retention (days)"
|
|
126
139
|
type="number"
|
|
127
|
-
min={MIN_AUDIT_LOG_RETENTION_DAYS}
|
|
140
|
+
min={auditChanged ? MIN_AUDIT_LOG_RETENTION_DAYS : undefined}
|
|
128
141
|
className={INPUT_CLASS}
|
|
129
142
|
value={auditLogRetentionDays}
|
|
130
143
|
onChange={(e) => setAuditLogRetentionDays(e.target.value)}
|