From 61bc7f18e4459b3ad7cca27db26924845d0c966b Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Tue, 4 Aug 2026 20:38:47 -0300 Subject: [PATCH] [MM-69647] Remove CloudDedicatedExportUI feature flag and dead code (#37836) The CloudDedicatedExportUI flag (default false, shipped v9.7.0) has been off everywhere since it shipped. It gated the Cloud Enterprise System Console "Export Storage" admin UI and the accompanying Test Connection resolution that picked the ExportDriverName/Export* credential fields over the primary file-store fields. Per the decision recorded on MM-69647, remove the feature. Because App.UseExportFileStore() has always returned false (the flag never flipped on), simply dropping the flag term would have left IsCloud() && DedicatedExportStore -- silently activating the never- exercised Export* resolution for cloud servers. Instead revert the MM-68854 dispatch entirely and lock in the long-standing primary-field behavior: - Server: remove UseExportFileStore()/ResolvedFileStoreDriverName(), collapse CheckMandatoryS3Fields/CheckMandatoryAzureFields and TestFileStoreConnectionWithConfig to the primary file-store fields, and restore testFileStore's primary DriverName dispatch. Drop the obsolete TestFileStoreTestConnection test. - Webapp: remove the "Export Storage" admin console section, its en.json strings, and the admin_sidebar fixtures/snapshot entry. - Remove the struct field, its SetDefaults entry, and the e2e mirror. The underlying FileSettings.DedicatedExportStore config and Export* fields are kept -- they remain a live config-file/env feature used by bulk and compliance exports, independent of the removed UI. --- .../lib/src/server/default_config.ts | 1 - server/channels/api4/system.go | 10 +- server/channels/api4/system_test.go | 81 ---- server/channels/app/file.go | 47 +-- server/public/model/feature_flags.go | 3 - .../admin_console/admin_definition.tsx | 346 ------------------ .../__snapshots__/admin_sidebar.test.tsx.snap | 38 -- .../admin_sidebar/admin_sidebar.test.tsx | 2 - webapp/channels/src/i18n/en.json | 11 - 9 files changed, 5 insertions(+), 534 deletions(-) diff --git a/e2e-tests/playwright/lib/src/server/default_config.ts b/e2e-tests/playwright/lib/src/server/default_config.ts index 9f63620d9fe..7101bf87b9d 100644 --- a/e2e-tests/playwright/lib/src/server/default_config.ts +++ b/e2e-tests/playwright/lib/src/server/default_config.ts @@ -804,7 +804,6 @@ const defaultServerConfig: AdminConfig = { WysiwygEditor: false, EnableExportDirectDownload: false, MoveThreadsEnabled: false, - CloudDedicatedExportUI: false, NotificationMonitoring: true, CustomProfileAttributes: true, AttributeValueMasking: false, diff --git a/server/channels/api4/system.go b/server/channels/api4/system.go index 4b96ba5f45c..15c26b055ab 100644 --- a/server/channels/api4/system.go +++ b/server/channels/api4/system.go @@ -591,12 +591,10 @@ func testFileStore(c *Context, w http.ResponseWriter, r *http.Request) { // Validate mandatory fields per driver. TestFileStoreConnectionWithConfig // will catch missing fields by failing to construct the backend, but a // dedicated validation step lets us surface a clearer error. - // - // When the dedicated export filestore is active the backend that is - // actually built and tested is the export backend, so we have to dispatch - // on ExportDriverName -- otherwise a primary=S3 / export=Azure deployment - // would run the S3 field check while testing the Azure backend. - driver := c.App.ResolvedFileStoreDriverName(&cfg.FileSettings) + driver := "" + if cfg.FileSettings.DriverName != nil { + driver = *cfg.FileSettings.DriverName + } switch driver { case model.ImageDriverLocal: // Local driver has no mandatory fields beyond the directory, which has a default. diff --git a/server/channels/api4/system_test.go b/server/channels/api4/system_test.go index c838b79575a..4bcd37cbf8b 100644 --- a/server/channels/api4/system_test.go +++ b/server/channels/api4/system_test.go @@ -826,87 +826,6 @@ func TestS3TestConnection(t *testing.T) { }) } -func TestFileStoreTestConnection(t *testing.T) { - mainHelper.Parallel(t) - th := SetupConfig(t, func(cfg *model.Config) { - cfg.FeatureFlags.CloudDedicatedExportUI = true - cfg.FileSettings.DedicatedExportStore = model.NewPointer(true) - }) - - th.App.Srv().SetLicense(model.NewTestLicense("cloud")) - t.Cleanup(func() { th.App.Srv().SetLicense(nil) }) - - baseSettings := func(primary, export string) model.FileSettings { - fs := model.FileSettings{} - fs.SetDefaults(false) - fs.DriverName = model.NewPointer(primary) - fs.ExportDriverName = model.NewPointer(export) - return fs - } - - t.Run("primary=local, export=azure: missing ExportAzureContainer surfaces azure error", func(t *testing.T) { - fs := baseSettings(model.ImageDriverLocal, model.ImageDriverAzure) - fs.ExportAzureStorageAccount = model.NewPointer("acmemattermost") - fs.ExportAzureAccessKey = model.NewPointer("secret") - fs.ExportAzureContainer = model.NewPointer("") - - resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &model.Config{FileSettings: fs}) - require.Error(t, err) - CheckErrorID(t, err, "api.admin.test_azure.missing_azure_field") - CheckBadRequestStatus(t, resp) - }) - - t.Run("primary=s3, export=azure: missing ExportAzureStorageAccount surfaces azure error", func(t *testing.T) { - fs := baseSettings(model.ImageDriverS3, model.ImageDriverAzure) - fs.ExportAzureStorageAccount = model.NewPointer("") - fs.ExportAzureContainer = model.NewPointer("mattermost") - fs.ExportAzureAccessKey = model.NewPointer("secret") - - resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &model.Config{FileSettings: fs}) - require.Error(t, err) - CheckErrorID(t, err, "api.admin.test_azure.missing_azure_field") - CheckBadRequestStatus(t, resp) - }) - - t.Run("primary=local, export=s3: missing ExportAmazonS3Bucket surfaces s3 error", func(t *testing.T) { - fs := baseSettings(model.ImageDriverLocal, model.ImageDriverS3) - fs.ExportAmazonS3Bucket = model.NewPointer("") - - resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &model.Config{FileSettings: fs}) - require.Error(t, err) - CheckErrorID(t, err, "api.admin.test_s3.missing_s3_bucket") - CheckBadRequestStatus(t, resp) - }) - - t.Run("unsupported export driver", func(t *testing.T) { - fs := baseSettings(model.ImageDriverS3, "bogus") - - resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &model.Config{FileSettings: fs}) - require.Error(t, err) - CheckErrorID(t, err, "api.file.test_connection_unsupported_driver.app_error") - CheckBadRequestStatus(t, resp) - }) - - t.Run("no license - primary dispatch", func(t *testing.T) { - // No license is set, so UseExportFileStore() must return false. Set an - // ExportDriverName that, if it were honored, would change the dispatch - // outcome -- this asserts that the primary path is taken. - appErr := th.App.Srv().RemoveLicense() - require.Nil(t, appErr) - fs := baseSettings(model.ImageDriverAzure, model.ImageDriverS3) - - // fs.AzureStorageAccount = model.NewPointer("") - // fs.AzureContainer = model.NewPointer("mattermost") - // fs.AzureAccessKey = model.NewPointer("secret") - // fs.ExportAmazonS3Bucket = model.NewPointer("export-bucket") - - resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &model.Config{FileSettings: fs}) - require.Error(t, err) - CheckErrorID(t, err, "api.admin.test_azure.missing_azure_field") - CheckBadRequestStatus(t, resp) - }) -} - func TestSupportedTimezones(t *testing.T) { mainHelper.Parallel(t) th := Setup(t) diff --git a/server/channels/app/file.go b/server/channels/app/file.go index 761b5d91543..0d19b456b0a 100644 --- a/server/channels/app/file.go +++ b/server/channels/app/file.go @@ -59,43 +59,8 @@ func (a *App) ExportFileBackend() filestore.FileBackend { return a.ch.exportFilestore } -// UseExportFileStore reports whether the dedicated export filestore is -// active. When true, callers reading the filestore configuration should -// resolve the export-side fields (ExportDriverName, ExportAmazonS3*, -// ExportAzure*, ...) rather than the primary fields. -func (a *App) UseExportFileStore() bool { - if !a.License().IsCloud() { - return false - } - if !a.Config().FeatureFlags.CloudDedicatedExportUI { - return false - } - dedicated := a.Config().FileSettings.DedicatedExportStore - return dedicated != nil && *dedicated -} - -// ResolvedFileStoreDriverName returns the driver name that callers should -// read for the active filestore -- ExportDriverName when the dedicated -// export filestore is enabled, otherwise the primary DriverName. The empty -// string is returned when neither pointer is set so callers can produce a -// dedicated "unsupported driver" error. -func (a *App) ResolvedFileStoreDriverName(settings *model.FileSettings) string { - name := settings.DriverName - if a.UseExportFileStore() { - name = settings.ExportDriverName - } - - if name == nil { - return "" - } - return *name -} - func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError { bucket := settings.AmazonS3Bucket - if a.UseExportFileStore() { - bucket = settings.ExportAmazonS3Bucket - } if bucket == nil || *bucket == "" { return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest) } @@ -107,12 +72,6 @@ func (a *App) CheckMandatoryAzureFields(settings *model.FileSettings) *model.App authMode := settings.AzureAuthMode accessKey := settings.AzureAccessKey container := settings.AzureContainer - if a.UseExportFileStore() { - storageAccount = settings.ExportAzureStorageAccount - authMode = settings.ExportAzureAuthMode - accessKey = settings.ExportAzureAccessKey - container = settings.ExportAzureContainer - } if storageAccount == nil || *storageAccount == "" { return model.NewAppError("CheckMandatoryAzureFields", "api.admin.test_azure.missing_azure_field", nil, "missing azure storage account setting", http.StatusBadRequest) } @@ -162,11 +121,7 @@ func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model. complianceEnabled := license != nil && *license.Features.Compliance allowInsecure := insecure != nil && *insecure allowedUntrustedInternalConnections := model.SafeDereference(a.Config().ServiceSettings.AllowedUntrustedInternalConnections) - if a.UseExportFileStore() { - backend, err = filestore.NewFileBackend(filestore.NewExportFileBackendSettingsFromConfig(cfg, complianceEnabled && license.IsCloud(), allowInsecure, allowedUntrustedInternalConnections)) - } else { - backend, err = filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(cfg, complianceEnabled, allowInsecure, allowedUntrustedInternalConnections)) - } + backend, err = filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(cfg, complianceEnabled, allowInsecure, allowedUntrustedInternalConnections)) if err != nil { return model.NewAppError("FileAttachmentBackend", "api.file.no_driver.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } diff --git a/server/public/model/feature_flags.go b/server/public/model/feature_flags.go index f4da2123371..80bf57bfe03 100644 --- a/server/public/model/feature_flags.go +++ b/server/public/model/feature_flags.go @@ -34,8 +34,6 @@ type FeatureFlags struct { MoveThreadsEnabled bool - CloudDedicatedExportUI bool - NotificationMonitoring bool CustomProfileAttributes bool @@ -165,7 +163,6 @@ func (f *FeatureFlags) SetDefaults() { f.WysiwygEditor = false f.EnableExportDirectDownload = false f.MoveThreadsEnabled = false - f.CloudDedicatedExportUI = false f.NotificationMonitoring = true f.CustomProfileAttributes = true f.AttributeValueMasking = true diff --git a/webapp/channels/src/components/admin_console/admin_definition.tsx b/webapp/channels/src/components/admin_console/admin_definition.tsx index 1bf3a418954..ca4d74c0f8a 100644 --- a/webapp/channels/src/components/admin_console/admin_definition.tsx +++ b/webapp/channels/src/components/admin_console/admin_definition.tsx @@ -1592,352 +1592,6 @@ 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.minLicenseTier(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: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - isHidden: it.stateEquals('FileSettings.DedicatedExportStore', false), - options: [ - { - value: FILE_STORAGE_DRIVER_S3, - display_name: defineMessage({id: 'admin.image.storeAmazonS3', defaultMessage: 'Amazon S3'}), - }, - { - value: FILE_STORAGE_DRIVER_AZURE, - display_name: defineMessage({id: 'admin.image.storeAzureBlob', defaultMessage: 'Azure Blob Storage'}), - }, - ], - }, - { - 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.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.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values - 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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.amazonS3BucketExampleExport', 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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 empty.'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), 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.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values - help_text_values: { - link: (msg: string) => ( - - {msg} - - ), - }, - help_text_markdown: false, - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - }, - { - type: 'text', - key: 'FileSettings.ExportAmazonS3StorageClass', - label: defineMessage({id: 'admin.image.amazonS3StorageClassTitle', defaultMessage: 'Amazon S3 Storage Class:'}), - help_text: defineMessage({id: 'admin.image.amazonS3StorageClassDescription', defaultMessage: 'Storage class for your S3 Compatible Storage provider. Defaults to empty.'}), - placeholder: defineMessage({id: 'admin.image.amazonS3StorageClassExample', defaultMessage: 'E.g.: "STANDARD" or "STANDARD_IA"'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - }, - { - type: 'dropdown', - key: 'FileSettings.ExportAzureCloud', - label: defineMessage({id: 'admin.image.azureCloudTitle', defaultMessage: 'Azure Cloud:'}), - help_text: defineMessage({id: 'admin.image.azureCloudDescription', defaultMessage: 'The Azure cloud to connect to. Choose "Azure Commercial" or "Azure Government" to use the well-known endpoint for that cloud; only the storage account name is required. Choose "Custom Endpoint" to point at an arbitrary host such as Azurite, a reverse proxy, or any other Azure cloud (for example Azure China).'}), - options: [ - { - value: AZURE_CLOUD_COMMERCIAL, - display_name: defineMessage({id: 'admin.image.azureCloudCommercial', defaultMessage: 'Azure Commercial'}), - }, - { - value: AZURE_CLOUD_GOVERNMENT, - display_name: defineMessage({id: 'admin.image.azureCloudGovernment', defaultMessage: 'Azure Government'}), - }, - { - value: AZURE_CLOUD_CUSTOM, - display_name: defineMessage({id: 'admin.image.azureCloudCustom', defaultMessage: 'Custom Endpoint'}), - }, - ], - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - }, - { - type: 'text', - key: 'FileSettings.ExportAzureStorageAccount', - label: defineMessage({id: 'admin.image.azureStorageAccountTitle', defaultMessage: 'Azure Storage Account:'}), - help_text: defineMessage({id: 'admin.image.azureStorageAccountDescription', defaultMessage: 'The name of your Azure Storage account.'}), - placeholder: defineMessage({id: 'admin.image.azureStorageAccountExample', defaultMessage: 'E.g.: "mattermoststorage"'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - }, - { - type: 'text', - key: 'FileSettings.ExportAzureContainer', - label: defineMessage({id: 'admin.image.azureContainerTitle', defaultMessage: 'Azure Container:'}), - help_text: defineMessage({id: 'admin.image.azureContainerExportDescription', defaultMessage: 'Name of the container in your Azure Storage account.'}), - placeholder: defineMessage({id: 'admin.image.azureContainerExportExample', 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.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - }, - { - type: 'text', - key: 'FileSettings.ExportAzurePathPrefix', - label: defineMessage({id: 'admin.image.azurePathPrefixTitle', defaultMessage: 'Azure Path Prefix:'}), - help_text: defineMessage({id: 'admin.image.azurePathPrefixDescription', defaultMessage: 'Optional path prefix to use for blobs in your Azure container.'}), - placeholder: defineMessage({id: 'admin.image.azurePathPrefixExample', defaultMessage: 'E.g.: "files/" or leave empty'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - }, - { - type: 'dropdown', - key: 'FileSettings.ExportAzureAuthMode', - label: defineMessage({id: 'admin.image.azureAuthModeTitle', defaultMessage: 'Azure Authentication:'}), - help_text: defineMessage({id: 'admin.image.azureAuthModeDescription', defaultMessage: '"Shared key" signs requests with the Storage Account access key.\n \n"Default credential (Microsoft Entra ID)" reads the identity from the host environment - managed identity on Azure-hosted deployments, workload identity, service principal env vars, or "az login" for local development. No access key required.'}), // eslint-disable-line formatjs/no-multiple-whitespaces - help_text_markdown: true, - options: [ - { - value: AZURE_AUTH_MODE_SHARED_KEY, - display_name: defineMessage({id: 'admin.image.azureAuthModeSharedKey', defaultMessage: 'Shared key'}), - }, - { - value: AZURE_AUTH_MODE_DEFAULT_CREDENTIAL, - display_name: defineMessage({id: 'admin.image.azureAuthModeDefaultCredential', defaultMessage: 'Default credential (Microsoft Entra ID)'}), - }, - ], - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - }, - { - type: 'text', - key: 'FileSettings.ExportAzureAccessKey', - label: defineMessage({id: 'admin.image.azureAccessKeyTitle', defaultMessage: 'Azure Storage Account Key:'}), - help_text: defineMessage({id: 'admin.image.azureAccessKeyDescription', defaultMessage: 'The shared key for your Azure Storage account.'}), - placeholder: defineMessage({id: 'admin.image.azureAccessKeyExample', defaultMessage: 'E.g.: "9MZbtYgfq18PJ8PbRaJ5u91IH8izHvReTbcuQzMl+So="'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - it.not(it.stateEquals('FileSettings.ExportAzureAuthMode', AZURE_AUTH_MODE_SHARED_KEY)), - ), - isHidden: it.any( - it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - it.not(it.stateEquals('FileSettings.ExportAzureAuthMode', AZURE_AUTH_MODE_SHARED_KEY)), - ), - }, - { - type: 'text', - key: 'FileSettings.ExportAzureEndpoint', - label: defineMessage({id: 'admin.image.azureEndpointTitle', defaultMessage: 'Custom Azure Endpoint:'}), - help_text: defineMessage({id: 'admin.image.azureEndpointDescription', defaultMessage: 'Full Blob service URL, including scheme and storage account. Mattermost does not modify this URL, so the storage account must already be embedded in the hostname (vhost-style, e.g. "https://acmemattermost.blob.core.chinacloudapi.cn/") or in the path (path-style, e.g. "http://localhost:10000/devstoreaccount1/"). Shared-key auth signs against the host this URL points at, so make sure it actually serves the storage account named above.'}), - placeholder: defineMessage({id: 'admin.image.azureEndpointExample', defaultMessage: 'E.g.: "http://localhost:10000/devstoreaccount1/"'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - it.not(it.stateEquals('FileSettings.ExportAzureCloud', AZURE_CLOUD_CUSTOM)), - ), - isHidden: it.any( - it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - it.not(it.stateEquals('FileSettings.ExportAzureCloud', AZURE_CLOUD_CUSTOM)), - ), - }, - { - type: 'bool', - key: 'FileSettings.ExportAzureSSL', - label: defineMessage({id: 'admin.image.azureSSLTitle', defaultMessage: 'Enable Secure Azure Blob Storage Connections:'}), - help_text: defineMessage({id: 'admin.image.azureSSLDescription', defaultMessage: 'When false, allow insecure connections to Azure Blob Storage. Defaults to secure connections only. Ignored for the Custom Endpoint cloud (the scheme is part of the endpoint URL).'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - it.stateEquals('FileSettings.ExportAzureCloud', AZURE_CLOUD_CUSTOM), - ), - isHidden: it.any( - it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - it.stateEquals('FileSettings.ExportAzureCloud', AZURE_CLOUD_CUSTOM), - ), - }, - { - type: 'number', - key: 'FileSettings.ExportAzureRequestTimeoutMilliseconds', - label: defineMessage({id: 'admin.image.azureRequestTimeoutTitle', defaultMessage: 'Azure Request Timeout (Milliseconds):'}), - help_text: defineMessage({id: 'admin.image.azureRequestTimeoutDescription', defaultMessage: 'Number of milliseconds to wait for a response from Azure Blob Storage before timing out.'}), - placeholder: defineMessage({id: 'admin.image.azureRequestTimeoutExample', defaultMessage: 'E.g.: "30000"'}), - isDisabled: it.any( - it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - it.stateEquals('FileSettings.DedicatedExportStore', false), - ), - isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), it.stateEquals('FileSettings.DedicatedExportStore', false)), - }, - { - type: 'button', - action: testFileStoreConnection, - key: 'TestFileStoreConnection', - label: defineMessage({id: 'admin.filestore.connectionTest', defaultMessage: 'Test Connection'}), - loading: defineMessage({id: 'admin.filestore.testing', defaultMessage: 'Testing...'}), - error_message: defineMessage({id: 'admin.filestore.testFail', defaultMessage: 'Connection unsuccessful: {error}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime - success_message: defineMessage({id: 'admin.filestore.testSuccess', defaultMessage: 'Connection was successful'}), - isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)), - isHidden: 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/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap b/webapp/channels/src/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap index d8abafed4e3..a14b927b97b 100644 --- a/webapp/channels/src/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap @@ -2814,25 +2814,6 @@ c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V4z M20,4v5H4V4H20z M22,15v5c0,1.1-0.9,2-2,2H class="nav nav__sub-menu subsections" /> -