diff --git a/server/channels/api4/system.go b/server/channels/api4/system.go
index 5103daccb7..bf234ccfeb 100644
--- a/server/channels/api4/system.go
+++ b/server/channels/api4/system.go
@@ -535,11 +535,6 @@ func testS3(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
- c.Err = model.NewAppError("testS3", "api.restricted_system_admin", nil, "", http.StatusForbidden)
- return
- }
-
appErr := c.App.CheckMandatoryS3Fields(&cfg.FileSettings)
if appErr != nil {
c.Err = appErr
diff --git a/server/channels/api4/system_test.go b/server/channels/api4/system_test.go
index d33a7a2993..9e6901cd8d 100644
--- a/server/channels/api4/system_test.go
+++ b/server/channels/api4/system_test.go
@@ -589,15 +589,6 @@ func TestS3TestConnection(t *testing.T) {
CheckErrorID(t, err, "api.file.test_connection_s3_auth.app_error")
})
- t.Run("as restricted system admin", func(t *testing.T) {
- th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
- defer th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = false })
-
- resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &config)
- require.Error(t, err)
- CheckForbiddenStatus(t, resp)
- })
-
t.Run("empty file settings", func(t *testing.T) {
config.FileSettings = model.FileSettings{}
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &config)
diff --git a/server/channels/app/file.go b/server/channels/app/file.go
index fddbe4483f..19de7b4ae0 100644
--- a/server/channels/app/file.go
+++ b/server/channels/app/file.go
@@ -57,7 +57,13 @@ func (a *App) ExportFileBackend() filestore.FileBackend {
}
func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
- fileBackendSettings := filestore.NewFileBackendSettingsFromConfig(settings, false, false)
+ var fileBackendSettings filestore.FileBackendSettings
+ if a.License().IsCloud() && a.Config().FeatureFlags.CloudDedicatedExportUI && a.Config().FileSettings.DedicatedExportStore != nil && *a.Config().FileSettings.DedicatedExportStore {
+ fileBackendSettings = filestore.NewExportFileBackendSettingsFromConfig(settings, false, false)
+ } else {
+ fileBackendSettings = filestore.NewFileBackendSettingsFromConfig(settings, false, false)
+ }
+
err := fileBackendSettings.CheckMandatoryS3Fields()
if err != nil {
return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest).Wrap(err)
@@ -87,7 +93,15 @@ func (a *App) TestFileStoreConnection() *model.AppError {
func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model.AppError {
license := a.Srv().License()
insecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections
- backend, err := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(cfg, license != nil && *license.Features.Compliance, insecure != nil && *insecure))
+ var backend filestore.FileBackend
+ var err error
+ complianceEnabled := license != nil && *license.Features.Compliance
+ if license.IsCloud() && a.Config().FeatureFlags.CloudDedicatedExportUI && a.Config().FileSettings.DedicatedExportStore != nil && *a.Config().FileSettings.DedicatedExportStore {
+ allowInsecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections != nil && *a.Config().ServiceSettings.EnableInsecureOutgoingConnections
+ backend, err = filestore.NewFileBackend(filestore.NewExportFileBackendSettingsFromConfig(cfg, complianceEnabled && license.IsCloud(), allowInsecure))
+ } else {
+ backend, err = filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(cfg, complianceEnabled, insecure != nil && *insecure))
+ }
if err != nil {
return model.NewAppError("FileBackend", "api.file.no_driver.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
diff --git a/server/public/model/config.go b/server/public/model/config.go
index 9fca265c98..ed4ff64a00 100644
--- a/server/public/model/config.go
+++ b/server/public/model/config.go
@@ -1600,21 +1600,21 @@ type FileSettings struct {
AmazonS3Trace *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
AmazonS3RequestTimeoutMilliseconds *int64 `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
// Export store settings
- DedicatedExportStore *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
- ExportDriverName *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
- ExportDirectory *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3AccessKeyId *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3SecretAccessKey *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3Bucket *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3PathPrefix *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3Region *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3Endpoint *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3SSL *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
- ExportAmazonS3SignV2 *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
- ExportAmazonS3SSE *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
- ExportAmazonS3Trace *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
- ExportAmazonS3RequestTimeoutMilliseconds *int64 `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
- ExportAmazonS3PresignExpiresSeconds *int64 `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
+ DedicatedExportStore *bool `access:"environment_file_storage,write_restrictable"`
+ ExportDriverName *string `access:"environment_file_storage,write_restrictable"`
+ ExportDirectory *string `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3AccessKeyId *string `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3SecretAccessKey *string `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3Bucket *string `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3PathPrefix *string `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3Region *string `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3Endpoint *string `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3SSL *bool `access:"environment_file_storage,write_restrictable"`
+ ExportAmazonS3SignV2 *bool `access:"environment_file_storage,write_restrictable"`
+ ExportAmazonS3SSE *bool `access:"environment_file_storage,write_restrictable"`
+ ExportAmazonS3Trace *bool `access:"environment_file_storage,write_restrictable"`
+ ExportAmazonS3RequestTimeoutMilliseconds *int64 `access:"environment_file_storage,write_restrictable"` // telemetry: none
+ ExportAmazonS3PresignExpiresSeconds *int64 `access:"environment_file_storage,write_restrictable"` // telemetry: none
}
func (s *FileSettings) SetDefaults(isUpdate bool) {
diff --git a/server/public/model/feature_flags.go b/server/public/model/feature_flags.go
index dfccfe43fd..c2f3ea4ce3 100644
--- a/server/public/model/feature_flags.go
+++ b/server/public/model/feature_flags.go
@@ -50,6 +50,9 @@ type FeatureFlags struct {
ConsumePostHook bool
CloudAnnualRenewals bool
+
+ CloudDedicatedExportUI bool
+
WebSocketEventScope bool
}
@@ -70,6 +73,7 @@ func (f *FeatureFlags) SetDefaults() {
f.CloudIPFiltering = false
f.ConsumePostHook = false
f.CloudAnnualRenewals = false
+ f.CloudDedicatedExportUI = false
f.WebSocketEventScope = false
}
diff --git a/webapp/channels/src/components/admin_console/admin_definition.tsx b/webapp/channels/src/components/admin_console/admin_definition.tsx
index 494eadaea8..f013517ef0 100644
--- a/webapp/channels/src/components/admin_console/admin_definition.tsx
+++ b/webapp/channels/src/components/admin_console/admin_definition.tsx
@@ -1130,6 +1130,191 @@ const AdminDefinition: AdminDefinitionType = {
],
},
},
+ export_storage: {
+ url: 'environment/export_storage',
+ title: defineMessage({id: 'admin.sidebar.exportStorage', defaultMessage: 'Export Storage'}),
+ isHidden: it.any(
+ it.not(it.licensedForFeature('Cloud')),
+ it.not(it.licensedForSku(LicenseSkus.Enterprise)),
+ it.configIsFalse('FeatureFlags', 'CloudDedicatedExportUI'),
+ ),
+ schema: {
+ id: 'ExportFileSettings',
+ name: defineMessage({id: 'admin.sidebar.exportStorage', defaultMessage: 'Export Storage'}),
+ settings: [
+ {
+ type: 'bool',
+ key: 'FileSettings.DedicatedExportStore',
+ label: defineMessage({id: 'admin.exportStorage.dedicatedExportStore', defaultMessage: 'Enable Dedicated Export Store:'}),
+ help_text: defineMessage({id: 'admin.exportStorage.dedicatedExportStoreDescription', defaultMessage: 'When enabled, Mattermost will use a dedicated export storage bucket for all export operations. This is required for Mattermost Cloud deployments.'}),
+ isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ },
+ {
+ type: 'dropdown',
+ key: 'FileSettings.ExportDriverName',
+ label: defineMessage({id: 'admin.exportStorage.exportDriverName', defaultMessage: 'Export Storage Driver:'}),
+ isDisabled: true,
+ isHidden: it.stateEquals('FileSettings.DedicatedExportStore', false),
+ options: [
+ {
+ value: FILE_STORAGE_DRIVER_S3,
+ display_name: defineMessage({id: 'admin.image.storeAmazonS3', defaultMessage: 'Amazon S3'}),
+ },
+ ],
+ },
+ {
+ type: 'text',
+ key: 'FileSettings.ExportDirectory',
+ label: defineMessage({id: 'admin.exportStorage.exportDirectory', defaultMessage: 'Export Directory'}),
+ help_text: defineMessage({id: 'admin.image.exportDirectoryDescription', defaultMessage: 'Directory to which files are written. If blank, defaults to ./data/.'}),
+ placeholder: defineMessage({id: 'admin.image.localExample', defaultMessage: 'E.g.: "./data/"'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'text',
+ key: 'FileSettings.ExportAmazonS3AccessKeyId',
+ label: defineMessage({id: 'admin.image.amazonS3IdTitle', defaultMessage: 'Amazon S3 Access Key ID:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3IdDescription', defaultMessage: '(Optional) Only required if you do not want to authenticate to S3 using an IAM role. Enter the Access Key ID provided by your Amazon EC2 administrator.'}),
+ help_text_values: {
+ link: (msg: string) => (
+
+ {msg}
+
+ ),
+ },
+ help_text_markdown: false,
+ placeholder: defineMessage({id: 'admin.image.amazonS3IdExample', defaultMessage: 'E.g.: "AKIADTOVBGERKLCBV"'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'text',
+ key: 'FileSettings.ExportAmazonS3SecretAccessKey',
+ label: defineMessage({id: 'admin.image.amazonS3SecretTitle', defaultMessage: 'Amazon S3 Secret Access Key:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3SecretDescription', defaultMessage: '(Optional) The secret access key associated with your Amazon S3 Access Key ID.'}),
+ placeholder: defineMessage({id: 'admin.image.amazonS3SecretExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'text',
+ key: 'FileSettings.ExportAmazonS3Bucket',
+ label: defineMessage({id: 'admin.image.amazonS3BucketTitle', defaultMessage: 'Amazon S3 Bucket:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3BucketDescription', defaultMessage: 'Name you selected for your S3 bucket in AWS.'}),
+ placeholder: defineMessage({id: 'admin.image.amazonS3BucketExample', defaultMessage: 'E.g.: "mattermost-export"'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'text',
+ key: 'FileSettings.ExportAmazonS3PathPrefix',
+ label: defineMessage({id: 'admin.image.amazonS3PathPrefixTitle', defaultMessage: 'Amazon S3 Path Prefix:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3PathPrefixDescription', defaultMessage: 'Prefix you selected for your S3 bucket in AWS.'}),
+ placeholder: defineMessage({id: 'admin.image.amazonS3PathPrefixExample', defaultMessage: 'E.g.: "subdir1/" or you can leave it .'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'text',
+ key: 'FileSettings.ExportAmazonS3Region',
+ label: defineMessage({id: 'admin.image.amazonS3RegionTitle', defaultMessage: 'Amazon S3 Region:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3RegionDescription', defaultMessage: 'AWS region you selected when creating your S3 bucket. If no region is set, Mattermost attempts to get the appropriate region from AWS, or sets it to "us-east-1" if none found.'}),
+ placeholder: defineMessage({id: 'admin.image.amazonS3RegionExample', defaultMessage: 'E.g.: "us-east-1"'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'text',
+ key: 'FileSettings.ExportAmazonS3Endpoint',
+ label: defineMessage({id: 'admin.image.amazonS3EndpointTitle', defaultMessage: 'Amazon S3 Endpoint:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3EndpointDescription', defaultMessage: 'Hostname of your S3 Compatible Storage provider. Defaults to "s3.amazonaws.com".'}),
+ placeholder: defineMessage({id: 'admin.image.amazonS3EndpointExample', defaultMessage: 'E.g.: "s3.amazonaws.com"'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'bool',
+ key: 'FileSettings.ExportAmazonS3SSL',
+ label: defineMessage({id: 'admin.image.amazonS3SSLTitle', defaultMessage: 'Enable Secure Amazon S3 Connections:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3SSLDescription', defaultMessage: 'When false, allow insecure connections to Amazon S3. Defaults to secure connections only.'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'bool',
+ key: 'FileSettings.ExportAmazonSignV2',
+ label: defineMessage({id: 'admin.image.amazonS3SignV2', defaultMessage: 'Enable Sign V2'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3SignV2Description', defaultMessage: 'When true, use Sign V2 for Amazon S3 connections'}),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ {
+ type: 'bool',
+ key: 'FileSettings.ExportAmazonS3SSE',
+ label: defineMessage({id: 'admin.image.amazonS3SSETitle', defaultMessage: 'Enable Server-Side Encryption for Amazon S3:'}),
+ help_text: defineMessage({id: 'admin.image.amazonS3SSEDescription', defaultMessage: 'When true, encrypt files in Amazon S3 using server-side encryption with Amazon S3-managed keys. See documentation to learn more.'}),
+ help_text_values: {
+ link: (msg: string) => (
+
+ {msg}
+
+ ),
+ },
+ help_text_markdown: false,
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ isDisabled: it.any(
+ it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ it.stateEquals('FileSettings.DedicatedExportStore', false),
+ ),
+ },
+ {
+ type: 'button',
+ action: testS3Connection,
+ key: 'TestS3Connection',
+ label: defineMessage({id: 'admin.s3.connectionS3Test', defaultMessage: 'Test Connection'}),
+ loading: defineMessage({id: 'admin.s3.testing', defaultMessage: 'Testing...'}),
+ error_message: defineMessage({id: 'admin.s3.s3Fail', defaultMessage: 'Connection unsuccessful: {error}'}),
+ success_message: defineMessage({id: 'admin.s3.s3Success', defaultMessage: 'Connection was successful'}),
+ isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
+ isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
+ },
+ ],
+ },
+ },
image_proxy: {
url: 'environment/image_proxy',
title: defineMessage({id: 'admin.sidebar.imageProxy', defaultMessage: 'Image Proxy'}),
diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json
index 6ef8465de5..0ca044f585 100644
--- a/webapp/channels/src/i18n/en.json
+++ b/webapp/channels/src/i18n/en.json
@@ -1033,6 +1033,10 @@
"admin.experimental.userStatusAwayTimeout.desc": "This setting defines the number of seconds after which the user’s status indicator changes to \"Away\", when they are away from Mattermost.",
"admin.experimental.userStatusAwayTimeout.example": "E.g.: \"300\"",
"admin.experimental.userStatusAwayTimeout.title": "User Status Away Timeout:",
+ "admin.exportStorage.dedicatedExportStore": "Enable Dedicated Export Store:",
+ "admin.exportStorage.dedicatedExportStoreDescription": "When enabled, Mattermost will use a dedicated export storage bucket for all export operations. This is required for Mattermost Cloud deployments.",
+ "admin.exportStorage.exportDirectory": "Export Directory",
+ "admin.exportStorage.exportDriverName": "Export Storage Driver:",
"admin.false": "false",
"admin.feature_discovery.trial-request.accept-terms": "By clicking Start trial, I agree to the Mattermost Software Evaluation Agreement, Privacy Policy and receiving product emails.",
"admin.feature_discovery.trial-request.accept-terms.cloudFree": "By selecting Try free for {trialLength} days, I agree to the Mattermost Software Evaluation Agreement, Privacy Policy, and receiving product emails.",
@@ -1187,6 +1191,8 @@
"admin.image.amazonS3SecretDescription": "(Optional) The secret access key associated with your Amazon S3 Access Key ID.",
"admin.image.amazonS3SecretExample": "E.g.: \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
"admin.image.amazonS3SecretTitle": "Amazon S3 Secret Access Key:",
+ "admin.image.amazonS3SignV2": "Enable Sign V2",
+ "admin.image.amazonS3SignV2Description": "When true, use Sign V2 for Amazon S3 connections",
"admin.image.amazonS3SSEDescription": "When true, encrypt files in Amazon S3 using server-side encryption with Amazon S3-managed keys. See documentation to learn more.",
"admin.image.amazonS3SSETitle": "Enable Server-Side Encryption for Amazon S3:",
"admin.image.amazonS3SSLDescription": "When false, allow insecure connections to Amazon S3. Defaults to secure connections only.",
@@ -1197,6 +1203,7 @@
"admin.image.archiveRecursionTitle": "Enable searching content of documents within ZIP files:",
"admin.image.enableProxy": "Enable Image Proxy:",
"admin.image.enableProxyDescription": "When true, enables an image proxy for loading all Markdown images.",
+ "admin.image.exportDirectoryDescription": "Directory to which files are written. If blank, defaults to ./data/.",
"admin.image.extractContentDescription": "When enabled, supported document types are searchable by their content. Search results for existing documents may be incomplete until a data migration is executed.",
"admin.image.extractContentTitle": "Enable document search by content:",
"admin.image.localDescription": "Directory to which files and images are written. If blank, defaults to ./data/.",
@@ -2361,6 +2368,7 @@
"admin.sidebar.environment": "Environment",
"admin.sidebar.experimental": "Experimental",
"admin.sidebar.experimentalFeatures": "Features",
+ "admin.sidebar.exportStorage": "Export Storage",
"admin.sidebar.fileSharingDownloads": "File Sharing and Downloads",
"admin.sidebar.fileStorage": "File Storage",
"admin.sidebar.filter": "Find settings",