[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.
This commit is contained in:
Jesse Hallam
2026-08-04 19:38:47 -04:00
committed by GitHub
parent 85acba42e1
commit 61bc7f18e4
9 changed files with 5 additions and 534 deletions
@@ -804,7 +804,6 @@ const defaultServerConfig: AdminConfig = {
WysiwygEditor: false,
EnableExportDirectDownload: false,
MoveThreadsEnabled: false,
CloudDedicatedExportUI: false,
NotificationMonitoring: true,
CustomProfileAttributes: true,
AttributeValueMasking: false,
+4 -6
View File
@@ -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.
-81
View File
@@ -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)
+1 -46
View File
@@ -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)
}
-3
View File
@@ -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
@@ -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 <link>IAM role</link>. 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) => (
<ExternalLink
location='admin_console'
href='https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html'
>
{msg}
</ExternalLink>
),
},
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 <link>documentation</link> to learn more.'}), // eslint-disable-line formatjs/enforce-placeholders -- link provided via help_text_values
help_text_values: {
link: (msg: string) => (
<ExternalLink
location='admin_console'
href={DocLinks.SESSION_LENGTHS}
>
{msg}
</ExternalLink>
),
},
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'}),
@@ -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"
/>
</li>
<li
class="sidebar-section"
data-testid="environment.export_storage"
>
<a
class="sidebar-section-title"
href="/admin_console/environment/export_storage"
id="environment/export_storage"
>
<span
class="sidebar-section-title__text"
>
Export Storage
</span>
</a>
<ul
class="nav nav__sub-menu subsections"
/>
</li>
<li
class="sidebar-section"
data-testid="environment.image_proxy"
@@ -4698,25 +4679,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"
/>
</li>
<li
class="sidebar-section"
data-testid="environment.export_storage"
>
<a
class="sidebar-section-title"
href="/admin_console/environment/export_storage"
id="environment/export_storage"
>
<span
class="sidebar-section-title__text"
>
Export Storage
</span>
</a>
<ul
class="nav nav__sub-menu subsections"
/>
</li>
<li
class="sidebar-section"
data-testid="environment.image_proxy"
@@ -399,7 +399,6 @@ describe('components/AdminSidebar', () => {
} as Office365Settings,
FeatureFlags: {
CustomProfileAttributes: true,
CloudDedicatedExportUI: true,
},
},
adminDefinition: AdminDefinition,
@@ -532,7 +531,6 @@ describe('components/AdminSidebar', () => {
} as Office365Settings,
FeatureFlags: {
CustomProfileAttributes: true,
CloudDedicatedExportUI: true,
},
},
adminDefinition: AdminDefinition,
-11
View File
@@ -1380,10 +1380,6 @@
"admin.experimental.userStatusAwayTimeout.title": "User Status Away Timeout:",
"admin.experimental.youtubeReferrerPolicy.desc": "When true, the referrer policy for embedded YouTube videos will be set to \"strict-origin-when-cross-origin\" which resolves issues where YouTube video previews display as unavailable, while balancing the need to protect user privacy with some degree of referral data to support web functionalities, like analytics, logging, and third-party integrations. When false, the referrer policy will be set to \"no-referrer\" which enhances user privacy by not disclosing the source URL, but limits the ability to track user engagement and traffic sources in analytics tools.",
"admin.experimental.youtubeReferrerPolicy.title": "YouTube Referrer Policy:",
"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.contact_sales": "Contact sales",
"admin.feature_discovery.learn_more": "Learn more",
@@ -1561,7 +1557,6 @@
"admin.guest_access.whitelistedDomainsTitle": "Whitelisted Guest Domains:",
"admin.image.amazonS3BucketDescription": "Name you selected for your S3 bucket in AWS.",
"admin.image.amazonS3BucketExample": "E.g.: \"mattermost-media\"",
"admin.image.amazonS3BucketExampleExport": "E.g.: \"mattermost-export\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
"admin.image.amazonS3EndpointDescription": "Hostname of your S3 Compatible Storage provider. Defaults to \"s3.amazonaws.com\".",
"admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
@@ -1578,8 +1573,6 @@
"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 <link>documentation</link> 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.",
@@ -1605,8 +1598,6 @@
"admin.image.azureCloudTitle": "Azure Cloud:",
"admin.image.azureContainerDescription": "Name of the container in your Azure Storage account.",
"admin.image.azureContainerExample": "E.g.: \"mattermost-media\"",
"admin.image.azureContainerExportDescription": "Name of the container in your Azure Storage account.",
"admin.image.azureContainerExportExample": "E.g.: \"mattermost-export\"",
"admin.image.azureContainerTitle": "Azure Container:",
"admin.image.azureEndpointDescription": "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.",
"admin.image.azureEndpointExample": "E.g.: \"http://localhost:10000/devstoreaccount1/\"",
@@ -1624,7 +1615,6 @@
"admin.image.azureStorageAccountTitle": "Azure Storage Account:",
"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 <link>until a data migration is executed</link>.",
"admin.image.extractContentTimeout.minValue": "Timeout must be 0 or greater.",
"admin.image.extractContentTimeoutDescription": "Maximum number of seconds spent extracting the searchable content of a single uploaded document. Extractions that exceed this limit are aborted to protect server performance. Set to 0 to disable the timeout.",
@@ -3307,7 +3297,6 @@
"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",