mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-08 20:18:09 -05:00
MM-68663: Admin console support and Test Connection generalization for Azure Blob Storage (#36583)
* Generalize the file storage Test Connection endpoint Replaces the S3-only /api/v4/file/s3_test handler with a backend-agnostic POST /api/v4/file/test that validates mandatory fields per driver and runs a write/read/delete probe against the configured backend. The legacy /file/s3_test route stays as a thin wrapper so existing clients keep working. The driver switch validates S3 and Azure mandatory fields explicitly, treats Local as a no-op (no required credentials), and rejects unknown or empty driver names with a 400 and a specific error code so admins get a useful message instead of a generic backend failure. Reuses config.Desanitize (renamed from the package-private desanitize) so the FakeSetting placeholder swap for secrets is shared with the PUT /api/v4/config save path. Adding a new driver-secret in the future only requires touching config.Desanitize once. Desanitize is also made nil-safe on every pointer dereference so callers can hand it a partial config without first running SetDefaults(). Mattermost-redux and the webapp client gain a corresponding TestFileStoreConnection method that the admin console action layer calls instead of the deprecated S3-specific method. ------ AI assisted commit * Wire Azure Blob Storage into the file storage admin console Adds the Azure Blob Storage option to the File Storage panel in the System Console. Selecting it enables Azure-specific fields for the storage account name, container, optional path prefix, shared key, optional endpoint override, secure-connections toggle, and request timeout. The fields are hidden and disabled when the driver is set to Local or S3, matching the existing pattern. Help text and placeholders are added in the webapp i18n catalog so admins see the same field labels documented in the admin guide. The same set of fields is repeated for the Files Export panel when DedicatedExportStore is enabled, keeping the export backend configurable independently of the primary file store. ------ AI assisted commit * Document /api/v4/file/test in the OpenAPI spec Adds the new backend-agnostic file storage Test Connection endpoint to the public OpenAPI surface. The request body is optional: callers that omit it test the running server configuration, callers that include a full AdminConfig test the supplied configuration without persisting anything. The deprecated /api/v4/file/s3_test endpoint is left unchanged in the spec for the existing S3-only flow. ------ AI assisted commit * Add UI-only Cypress coverage for the Azure file storage panel Adds a Cypress spec that drives the System Console File Storage panel, switches the driver to Azure Blob Storage, fills in the Azure fields, and asserts the expected fields appear (and S3 fields are hidden). The spec is UI-only and does not depend on an Azure backend or Azurite, so it can run in CI without external infrastructure. Updates the existing environment_spec.js so it tolerates the new Azure option in the driver dropdown. ------ AI assisted commit * Nil-guard file storage mandatory-field checks CheckMandatoryS3Fields and CheckMandatoryAzureFields built a FileBackendSettings via NewFileBackendSettingsFromConfig before validating, but that constructor dereferences pointers unconditionally and would panic if a caller skipped the api handler's reflective nil check. Validate the required pointers directly against FileSettings instead, dropping the throwaway constructor call so the methods are safe to call from any path. ------ AI assisted commit * Check permission before validating file settings The /file/test handler ran checkHasNilFields before SessionHasPermissionTo, so an unauthorized caller posting a partial config got a 400, leaking config shape, rather than a 403. Swap the two blocks so the permission decision happens first. ------ AI assisted commit * Preserve FakeSetting when desanitize has no actual The Azure access key, export Azure access key, and S3 secret access key branches in Desanitize reassigned target to actual without checking actual for nil. When the running config had no value, the FakeSetting placeholder in target was replaced with nil, dropping the field from the round-trip. Guard the assignment so the placeholder stays in place when actual is unset. ------ AI assisted commit --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
co-authored by
Mattermost Build
parent
4d8c25f040
commit
c6b59cc9a7
@@ -550,22 +550,60 @@
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
/api/v4/file/test:
|
||||
post:
|
||||
tags:
|
||||
- system
|
||||
summary: Test the configured file storage backend
|
||||
description: >
|
||||
Send a test to validate that the server can connect to the configured
|
||||
file storage backend (Amazon S3 or Azure Blob Storage). Optionally
|
||||
provide a configuration in the request body to test. If no valid
|
||||
configuration is present in the request body the current server
|
||||
configuration will be tested.
|
||||
|
||||
##### Permissions
|
||||
|
||||
Must have `manage_system` permission.
|
||||
|
||||
__Minimum server version__: 11.10
|
||||
operationId: TestFileStoreConnection
|
||||
requestBody:
|
||||
description: Mattermost configuration
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Config"
|
||||
responses:
|
||||
"200":
|
||||
description: File storage test successful
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StatusOK"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
/api/v4/file/s3_test:
|
||||
post:
|
||||
tags:
|
||||
- system
|
||||
summary: Test AWS S3 connection
|
||||
description: >
|
||||
Send a test to validate if can connect to AWS S3. Optionally provide a
|
||||
configuration in the request body to test. If no valid configuration is
|
||||
present in the request body the current server configuration will be
|
||||
tested.
|
||||
Deprecated alias for `/api/v4/file/test` kept for backwards
|
||||
compatibility. New callers should use `/api/v4/file/test`, which is
|
||||
backend-agnostic.
|
||||
|
||||
##### Permissions
|
||||
|
||||
Must have `manage_system` permission.
|
||||
|
||||
__Minimum server version__: 4.8
|
||||
deprecated: true
|
||||
operationId: TestS3Connection
|
||||
requestBody:
|
||||
description: Mattermost configuration
|
||||
@@ -581,6 +619,8 @@
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StatusOK"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @not_cloud @system_console
|
||||
|
||||
describe('Environment - File Storage (Azure Blob Storage)', () => {
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/admin_console/environment/file_storage');
|
||||
cy.findByTestId('FileSettings.DriverNamedropdown').should('be.visible');
|
||||
});
|
||||
|
||||
it('shows the Azure Blob Storage option in the File Storage System dropdown', () => {
|
||||
// * Verify the Azure option is present alongside Local and S3
|
||||
cy.findByTestId('FileSettings.DriverNamedropdown').
|
||||
find('option[value="azureblob"]').
|
||||
should('have.text', 'Azure Blob Storage');
|
||||
});
|
||||
|
||||
it('enables Azure-only fields and disables S3-only fields when Azure is selected', () => {
|
||||
// # Select the Azure driver
|
||||
cy.findByTestId('FileSettings.DriverNamedropdown').select('azureblob');
|
||||
|
||||
// * Azure fields are enabled
|
||||
cy.findByTestId('FileSettings.AzureStorageAccountinput').should('not.be.disabled');
|
||||
cy.findByTestId('FileSettings.AzureContainerinput').should('not.be.disabled');
|
||||
cy.findByTestId('FileSettings.AzurePathPrefixinput').should('not.be.disabled');
|
||||
cy.findByTestId('FileSettings.AzureAccessKeyinput').should('not.be.disabled');
|
||||
cy.findByTestId('FileSettings.AzureEndpointinput').should('not.be.disabled');
|
||||
cy.findByTestId('FileSettings.AzureRequestTimeoutMillisecondsnumber').should('not.be.disabled');
|
||||
|
||||
// * S3 fields are disabled when the driver is not S3
|
||||
cy.findByTestId('FileSettings.AmazonS3Bucketinput').should('be.disabled');
|
||||
cy.findByTestId('FileSettings.AmazonS3AccessKeyIdinput').should('be.disabled');
|
||||
|
||||
// * Local directory is also disabled
|
||||
cy.findByTestId('FileSettings.Directoryinput').should('be.disabled');
|
||||
});
|
||||
|
||||
it('hides Azure-only fields when the S3 driver is selected', () => {
|
||||
// # Select the S3 driver
|
||||
cy.findByTestId('FileSettings.DriverNamedropdown').select('amazons3');
|
||||
|
||||
// * Azure fields are not rendered when the driver is not Azure
|
||||
cy.findByTestId('FileSettings.AzureStorageAccountinput').should('not.exist');
|
||||
cy.findByTestId('FileSettings.AzureContainerinput').should('not.exist');
|
||||
cy.findByTestId('FileSettings.AzureAccessKeyinput').should('not.exist');
|
||||
});
|
||||
|
||||
it('exposes the backend-agnostic Test Connection button when Azure is selected', () => {
|
||||
// # Select the Azure driver
|
||||
cy.findByTestId('FileSettings.DriverNamedropdown').select('azureblob');
|
||||
|
||||
// * The renamed button is rendered and is no longer S3-named
|
||||
cy.get('#TestFileStoreConnection').scrollIntoView().should('be.visible');
|
||||
cy.get('#TestFileStoreConnection').findByText('Test Connection').should('exist');
|
||||
cy.get('#TestS3Connection').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -243,7 +243,7 @@ describe('Environment', () => {
|
||||
// # Click Save button to save the settings
|
||||
cy.get('#saveSetting').click().wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
cy.get('#TestS3Connection').scrollIntoView().should('be.visible').within(() => {
|
||||
cy.get('#TestFileStoreConnection').scrollIntoView().should('be.visible').within(() => {
|
||||
cy.findByText('Test Connection').should('be.visible').click().wait(TIMEOUTS.ONE_SEC);
|
||||
waitForAlert('Connection unsuccessful: S3 Bucket is required');
|
||||
});
|
||||
@@ -254,7 +254,7 @@ describe('Environment', () => {
|
||||
// # Click Save button to save the settings
|
||||
cy.get('#saveSetting').click().wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
cy.get('#TestS3Connection').scrollIntoView().should('be.visible').within(() => {
|
||||
cy.get('#TestFileStoreConnection').scrollIntoView().should('be.visible').within(() => {
|
||||
cy.findByText('Test Connection').should('be.visible').click().wait(TIMEOUTS.ONE_SEC);
|
||||
waitForAlert('Connection unsuccessful: Unable to authenticate against the file storage backend. Verify your credentials and authentication settings.');
|
||||
});
|
||||
|
||||
@@ -47,7 +47,9 @@ func (api *API) InitSystem() {
|
||||
api.BaseRoutes.APIRoot.Handle("/notifications/test", api.APISessionRequired(testNotifications)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/email/test", api.APISessionRequired(testEmail)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/site_url/test", api.APISessionRequired(testSiteURL)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/file/s3_test", api.APISessionRequired(testS3)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/file/test", api.APISessionRequired(testFileStore)).Methods(http.MethodPost)
|
||||
// Deprecated: use /file/test instead. Kept as a thin compatibility wrapper.
|
||||
api.BaseRoutes.APIRoot.Handle("/file/s3_test", api.APISessionRequired(testFileStore)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/database/recycle", api.APISessionRequired(databaseRecycle)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.APIRoot.Handle("/caches/invalidate", api.APISessionRequired(invalidateCaches)).Methods(http.MethodPost)
|
||||
|
||||
@@ -560,7 +562,7 @@ func getSupportedTimezones(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func testS3(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func testFileStore(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var cfg *model.Config
|
||||
err := json.NewDecoder(r.Body).Decode(&cfg)
|
||||
if err != nil {
|
||||
@@ -570,28 +572,51 @@ func testS3(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
cfg = c.App.Config()
|
||||
}
|
||||
|
||||
if checkHasNilFields(&cfg.FileSettings) {
|
||||
c.Err = model.NewAppError("testS3", "api.file.test_connection_s3_settings_nil.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// PermissionTestS3 is kept for backwards compatibility -- it was named
|
||||
// after the only supported test endpoint at the time it was introduced.
|
||||
// The new /file/test endpoint is backend-agnostic but reuses the same
|
||||
// permission to avoid a role migration.
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionTestS3) {
|
||||
c.SetPermissionError(model.PermissionTestS3)
|
||||
return
|
||||
}
|
||||
|
||||
appErr := c.App.CheckMandatoryS3Fields(&cfg.FileSettings)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
if checkHasNilFields(&cfg.FileSettings) {
|
||||
c.Err = model.NewAppError("testFileStore", "api.file.test_connection_settings_nil.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if *cfg.FileSettings.AmazonS3SecretAccessKey == model.FakeSetting {
|
||||
cfg.FileSettings.AmazonS3SecretAccessKey = c.App.Config().FileSettings.AmazonS3SecretAccessKey
|
||||
// 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.
|
||||
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.
|
||||
case model.ImageDriverS3:
|
||||
if appErr := c.App.CheckMandatoryS3Fields(&cfg.FileSettings); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
case model.ImageDriverAzure:
|
||||
if appErr := c.App.CheckMandatoryAzureFields(&cfg.FileSettings); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.Err = model.NewAppError("testFileStore", "api.file.test_connection_unsupported_driver.app_error", map[string]any{"Driver": driver}, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
appErr = c.App.TestFileStoreConnectionWithConfig(&cfg.FileSettings)
|
||||
if appErr != nil {
|
||||
// A client editing the admin UI and clicking Test Connection without
|
||||
// re-entering the secret would send the FakeSetting placeholder back, so we
|
||||
// need to desanitize this first.
|
||||
config.Desanitize(c.App.Config(), cfg)
|
||||
|
||||
if appErr := c.App.TestFileStoreConnectionWithConfig(&cfg.FileSettings); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
@@ -725,9 +725,105 @@ func TestS3TestConnection(t *testing.T) {
|
||||
config.FileSettings = model.FileSettings{}
|
||||
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &config)
|
||||
require.Error(t, err)
|
||||
CheckErrorID(t, err, "api.file.test_connection_s3_settings_nil.app_error")
|
||||
CheckErrorID(t, err, "api.file.test_connection_settings_nil.app_error")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("desanitizes FakeSetting using running config", func(t *testing.T) {
|
||||
// Seed the running config with valid Minio credentials so the
|
||||
// running config's AmazonS3SecretAccessKey is the real secret.
|
||||
th.App.UpdateConfig(func(c *model.Config) {
|
||||
c.FileSettings.DriverName = model.NewPointer(model.ImageDriverS3)
|
||||
c.FileSettings.AmazonS3AccessKeyId = model.NewPointer(model.MinioAccessKey)
|
||||
c.FileSettings.AmazonS3SecretAccessKey = model.NewPointer(model.MinioSecretKey)
|
||||
c.FileSettings.AmazonS3Bucket = model.NewPointer(model.MinioBucket)
|
||||
c.FileSettings.AmazonS3Endpoint = model.NewPointer(s3Endpoint)
|
||||
c.FileSettings.AmazonS3Region = model.NewPointer("us-east-1")
|
||||
c.FileSettings.AmazonS3PathPrefix = model.NewPointer("")
|
||||
c.FileSettings.AmazonS3SSL = model.NewPointer(false)
|
||||
})
|
||||
|
||||
// Build a request body that mirrors what the System Console sends
|
||||
// after the admin clicks Test Connection without re-entering the
|
||||
// secret: every field present, but the secret slot is the
|
||||
// FakeSetting placeholder.
|
||||
body := model.Config{FileSettings: model.FileSettings{}}
|
||||
body.FileSettings.SetDefaults(false)
|
||||
body.FileSettings.DriverName = model.NewPointer(model.ImageDriverS3)
|
||||
body.FileSettings.AmazonS3AccessKeyId = model.NewPointer(model.MinioAccessKey)
|
||||
body.FileSettings.AmazonS3SecretAccessKey = model.NewPointer(model.FakeSetting)
|
||||
body.FileSettings.AmazonS3Bucket = model.NewPointer(model.MinioBucket)
|
||||
body.FileSettings.AmazonS3Endpoint = model.NewPointer(s3Endpoint)
|
||||
body.FileSettings.AmazonS3Region = model.NewPointer("us-east-1")
|
||||
body.FileSettings.AmazonS3PathPrefix = model.NewPointer("")
|
||||
body.FileSettings.AmazonS3SSL = model.NewPointer(false)
|
||||
|
||||
// If desanitize is not running, the server tests with the literal
|
||||
// "********" string as the secret and Minio returns a 403 auth
|
||||
// error. A 200 here proves the placeholder was swapped for the
|
||||
// real running-config value before the connection test.
|
||||
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &body)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("unsupported driver", func(t *testing.T) {
|
||||
unsupported := model.FileSettings{}
|
||||
unsupported.SetDefaults(false)
|
||||
unsupported.DriverName = model.NewPointer("bogus")
|
||||
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &model.Config{FileSettings: unsupported})
|
||||
require.Error(t, err)
|
||||
CheckErrorID(t, err, "api.file.test_connection_unsupported_driver.app_error")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("empty driver name", func(t *testing.T) {
|
||||
empty := model.FileSettings{}
|
||||
empty.SetDefaults(false)
|
||||
empty.DriverName = model.NewPointer("")
|
||||
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &model.Config{FileSettings: empty})
|
||||
require.Error(t, err)
|
||||
CheckErrorID(t, err, "api.file.test_connection_unsupported_driver.app_error")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("azure missing mandatory fields", func(t *testing.T) {
|
||||
// CheckMandatoryAzureFields rejects requests that don't carry an
|
||||
// Azure storage account, container, and access key. Each missing
|
||||
// field path must produce the same 400 with the dedicated error
|
||||
// ID so admins get a clear signal in the System Console toast.
|
||||
base := model.FileSettings{}
|
||||
base.SetDefaults(false)
|
||||
base.DriverName = model.NewPointer(model.ImageDriverAzure)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
mut func(*model.FileSettings)
|
||||
}{
|
||||
{"missing storage account", func(fs *model.FileSettings) {
|
||||
fs.AzureContainer = model.NewPointer("mattermost")
|
||||
fs.AzureAccessKey = model.NewPointer("secret")
|
||||
}},
|
||||
{"missing container", func(fs *model.FileSettings) {
|
||||
fs.AzureStorageAccount = model.NewPointer("acmemattermost")
|
||||
fs.AzureAccessKey = model.NewPointer("secret")
|
||||
}},
|
||||
{"missing access key", func(fs *model.FileSettings) {
|
||||
fs.AzureStorageAccount = model.NewPointer("acmemattermost")
|
||||
fs.AzureContainer = model.NewPointer("mattermost")
|
||||
}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fs := base
|
||||
tc.mut(&fs)
|
||||
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) {
|
||||
|
||||
@@ -60,16 +60,33 @@ func (a *App) ExportFileBackend() filestore.FileBackend {
|
||||
}
|
||||
|
||||
func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
|
||||
var fileBackendSettings filestore.FileBackendSettings
|
||||
bucket := settings.AmazonS3Bucket
|
||||
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)
|
||||
bucket = settings.ExportAmazonS3Bucket
|
||||
}
|
||||
if bucket == nil || *bucket == "" {
|
||||
return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := fileBackendSettings.CheckMandatoryS3Fields()
|
||||
if err != nil {
|
||||
return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
func (a *App) CheckMandatoryAzureFields(settings *model.FileSettings) *model.AppError {
|
||||
storageAccount := settings.AzureStorageAccount
|
||||
accessKey := settings.AzureAccessKey
|
||||
container := settings.AzureContainer
|
||||
if a.License().IsCloud() && a.Config().FeatureFlags.CloudDedicatedExportUI && a.Config().FileSettings.DedicatedExportStore != nil && *a.Config().FileSettings.DedicatedExportStore {
|
||||
storageAccount = settings.ExportAzureStorageAccount
|
||||
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)
|
||||
}
|
||||
if container == nil || *container == "" {
|
||||
return model.NewAppError("CheckMandatoryAzureFields", "api.admin.test_azure.missing_azure_field", nil, "missing azure container setting", http.StatusBadRequest)
|
||||
}
|
||||
if accessKey == nil || *accessKey == "" {
|
||||
return model.NewAppError("CheckMandatoryAzureFields", "api.admin.test_azure.missing_azure_field", nil, "missing azure access key setting", http.StatusBadRequest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func (s *Store) Set(newCfg *model.Config) (*model.Config, *model.Config, error)
|
||||
|
||||
// Sometimes the config is received with "fake" data in sensitive fields. Apply the real
|
||||
// data from the existing config as necessary.
|
||||
desanitize(oldCfg, newCfg)
|
||||
Desanitize(oldCfg, newCfg)
|
||||
|
||||
// We apply back environment overrides since the input config may or
|
||||
// may not have them applied.
|
||||
|
||||
+21
-15
@@ -21,33 +21,35 @@ func marshalConfig(cfg *model.Config) ([]byte, error) {
|
||||
return json.MarshalIndent(cfg, "", " ")
|
||||
}
|
||||
|
||||
// desanitize replaces fake settings with their actual values.
|
||||
func desanitize(actual, target *model.Config) {
|
||||
if target.LdapSettings.BindPassword != nil && *target.LdapSettings.BindPassword == model.FakeSetting {
|
||||
// Desanitize replaces fake settings with their actual values. Safe to call on
|
||||
// partial configs: every pointer dereference is nil-guarded so callers do not
|
||||
// need to run SetDefaults() first.
|
||||
func Desanitize(actual, target *model.Config) {
|
||||
if target.LdapSettings.BindPassword != nil && *target.LdapSettings.BindPassword == model.FakeSetting && actual.LdapSettings.BindPassword != nil {
|
||||
*target.LdapSettings.BindPassword = *actual.LdapSettings.BindPassword
|
||||
}
|
||||
|
||||
if *target.FileSettings.PublicLinkSalt == model.FakeSetting {
|
||||
if target.FileSettings.PublicLinkSalt != nil && *target.FileSettings.PublicLinkSalt == model.FakeSetting && actual.FileSettings.PublicLinkSalt != nil {
|
||||
*target.FileSettings.PublicLinkSalt = *actual.FileSettings.PublicLinkSalt
|
||||
}
|
||||
if *target.FileSettings.AmazonS3SecretAccessKey == model.FakeSetting {
|
||||
if target.FileSettings.AmazonS3SecretAccessKey != nil && *target.FileSettings.AmazonS3SecretAccessKey == model.FakeSetting && actual.FileSettings.AmazonS3SecretAccessKey != nil {
|
||||
target.FileSettings.AmazonS3SecretAccessKey = actual.FileSettings.AmazonS3SecretAccessKey
|
||||
}
|
||||
if target.FileSettings.ExportAmazonS3SecretAccessKey != nil && *target.FileSettings.ExportAmazonS3SecretAccessKey == model.FakeSetting {
|
||||
if target.FileSettings.ExportAmazonS3SecretAccessKey != nil && *target.FileSettings.ExportAmazonS3SecretAccessKey == model.FakeSetting && actual.FileSettings.ExportAmazonS3SecretAccessKey != nil {
|
||||
target.FileSettings.ExportAmazonS3SecretAccessKey = actual.FileSettings.ExportAmazonS3SecretAccessKey
|
||||
}
|
||||
if target.FileSettings.AzureAccessKey != nil && *target.FileSettings.AzureAccessKey == model.FakeSetting {
|
||||
if target.FileSettings.AzureAccessKey != nil && *target.FileSettings.AzureAccessKey == model.FakeSetting && actual.FileSettings.AzureAccessKey != nil {
|
||||
target.FileSettings.AzureAccessKey = actual.FileSettings.AzureAccessKey
|
||||
}
|
||||
if target.FileSettings.ExportAzureAccessKey != nil && *target.FileSettings.ExportAzureAccessKey == model.FakeSetting {
|
||||
if target.FileSettings.ExportAzureAccessKey != nil && *target.FileSettings.ExportAzureAccessKey == model.FakeSetting && actual.FileSettings.ExportAzureAccessKey != nil {
|
||||
target.FileSettings.ExportAzureAccessKey = actual.FileSettings.ExportAzureAccessKey
|
||||
}
|
||||
|
||||
if *target.EmailSettings.SMTPPassword == model.FakeSetting {
|
||||
if target.EmailSettings.SMTPPassword != nil && *target.EmailSettings.SMTPPassword == model.FakeSetting {
|
||||
target.EmailSettings.SMTPPassword = actual.EmailSettings.SMTPPassword
|
||||
}
|
||||
|
||||
if *target.GitLabSettings.Secret == model.FakeSetting {
|
||||
if target.GitLabSettings.Secret != nil && *target.GitLabSettings.Secret == model.FakeSetting {
|
||||
target.GitLabSettings.Secret = actual.GitLabSettings.Secret
|
||||
}
|
||||
|
||||
@@ -63,14 +65,14 @@ func desanitize(actual, target *model.Config) {
|
||||
target.OpenIdSettings.Secret = actual.OpenIdSettings.Secret
|
||||
}
|
||||
|
||||
if *target.SqlSettings.DataSource == model.FakeSetting {
|
||||
if target.SqlSettings.DataSource != nil && *target.SqlSettings.DataSource == model.FakeSetting && actual.SqlSettings.DataSource != nil {
|
||||
*target.SqlSettings.DataSource = *actual.SqlSettings.DataSource
|
||||
}
|
||||
if *target.SqlSettings.AtRestEncryptKey == model.FakeSetting {
|
||||
if target.SqlSettings.AtRestEncryptKey != nil && *target.SqlSettings.AtRestEncryptKey == model.FakeSetting {
|
||||
target.SqlSettings.AtRestEncryptKey = actual.SqlSettings.AtRestEncryptKey
|
||||
}
|
||||
|
||||
if *target.ElasticsearchSettings.Password == model.FakeSetting {
|
||||
if target.ElasticsearchSettings.Password != nil && *target.ElasticsearchSettings.Password == model.FakeSetting && actual.ElasticsearchSettings.Password != nil {
|
||||
*target.ElasticsearchSettings.Password = *actual.ElasticsearchSettings.Password
|
||||
}
|
||||
|
||||
@@ -90,11 +92,15 @@ func desanitize(actual, target *model.Config) {
|
||||
}
|
||||
}
|
||||
|
||||
if *target.MessageExportSettings.GlobalRelaySettings.SMTPPassword == model.FakeSetting {
|
||||
if target.MessageExportSettings.GlobalRelaySettings != nil &&
|
||||
target.MessageExportSettings.GlobalRelaySettings.SMTPPassword != nil &&
|
||||
*target.MessageExportSettings.GlobalRelaySettings.SMTPPassword == model.FakeSetting &&
|
||||
actual.MessageExportSettings.GlobalRelaySettings != nil &&
|
||||
actual.MessageExportSettings.GlobalRelaySettings.SMTPPassword != nil {
|
||||
*target.MessageExportSettings.GlobalRelaySettings.SMTPPassword = *actual.MessageExportSettings.GlobalRelaySettings.SMTPPassword
|
||||
}
|
||||
|
||||
if *target.ServiceSettings.SplitKey == model.FakeSetting {
|
||||
if target.ServiceSettings.SplitKey != nil && *target.ServiceSettings.SplitKey == model.FakeSetting && actual.ServiceSettings.SplitKey != nil {
|
||||
*target.ServiceSettings.SplitKey = *actual.ServiceSettings.SplitKey
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ func TestDesanitize(t *testing.T) {
|
||||
actual.FileSettings.PublicLinkSalt = new("public_link_salt")
|
||||
actual.FileSettings.AmazonS3SecretAccessKey = new("amazon_s3_secret_access_key")
|
||||
actual.FileSettings.ExportAmazonS3SecretAccessKey = new("export_amazon_s3_secret_access_key")
|
||||
actual.FileSettings.AzureAccessKey = new("azure_access_key")
|
||||
actual.FileSettings.ExportAzureAccessKey = new("export_azure_access_key")
|
||||
actual.EmailSettings.SMTPPassword = new("smtp_password")
|
||||
actual.GitLabSettings.Secret = new("secret")
|
||||
actual.OpenIdSettings.Secret = new("secret")
|
||||
@@ -59,6 +61,8 @@ func TestDesanitize(t *testing.T) {
|
||||
target.FileSettings.PublicLinkSalt = model.NewPointer(model.FakeSetting)
|
||||
target.FileSettings.AmazonS3SecretAccessKey = model.NewPointer(model.FakeSetting)
|
||||
target.FileSettings.ExportAmazonS3SecretAccessKey = model.NewPointer(model.FakeSetting)
|
||||
target.FileSettings.AzureAccessKey = model.NewPointer(model.FakeSetting)
|
||||
target.FileSettings.ExportAzureAccessKey = model.NewPointer(model.FakeSetting)
|
||||
target.EmailSettings.SMTPPassword = model.NewPointer(model.FakeSetting)
|
||||
target.GitLabSettings.Secret = model.NewPointer(model.FakeSetting)
|
||||
target.OpenIdSettings.Secret = model.NewPointer(model.FakeSetting)
|
||||
@@ -77,7 +81,7 @@ func TestDesanitize(t *testing.T) {
|
||||
}
|
||||
|
||||
actualClone := actual.Clone()
|
||||
desanitize(actual, target)
|
||||
Desanitize(actual, target)
|
||||
assert.Equal(t, actualClone, actual, "actual should not have been changed")
|
||||
|
||||
// Verify the settings that should have been left untouched in target
|
||||
@@ -89,6 +93,8 @@ func TestDesanitize(t *testing.T) {
|
||||
assert.Equal(t, *actual.FileSettings.PublicLinkSalt, *target.FileSettings.PublicLinkSalt)
|
||||
assert.Equal(t, *actual.FileSettings.AmazonS3SecretAccessKey, *target.FileSettings.AmazonS3SecretAccessKey)
|
||||
assert.Equal(t, *actual.FileSettings.ExportAmazonS3SecretAccessKey, *target.FileSettings.ExportAmazonS3SecretAccessKey)
|
||||
assert.Equal(t, *actual.FileSettings.AzureAccessKey, *target.FileSettings.AzureAccessKey)
|
||||
assert.Equal(t, *actual.FileSettings.ExportAzureAccessKey, *target.FileSettings.ExportAzureAccessKey)
|
||||
assert.Equal(t, *actual.EmailSettings.SMTPPassword, *target.EmailSettings.SMTPPassword)
|
||||
assert.Equal(t, *actual.GitLabSettings.Secret, *target.GitLabSettings.Secret)
|
||||
assert.Equal(t, *actual.OpenIdSettings.Secret, *target.OpenIdSettings.Secret)
|
||||
@@ -116,7 +122,7 @@ func TestDesanitizeRemovesAllFakeSettings(t *testing.T) {
|
||||
sanitized := actual.Clone()
|
||||
sanitized.Sanitize(nil, nil)
|
||||
|
||||
desanitize(actual, sanitized)
|
||||
Desanitize(actual, sanitized)
|
||||
|
||||
assertNoFakeSettings(t, reflect.ValueOf(*sanitized), "Config")
|
||||
}
|
||||
|
||||
+10
-2
@@ -187,6 +187,10 @@
|
||||
"id": "api.admin.syncables_error",
|
||||
"translation": "failed to add user to group-teams and group-channels"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.test_azure.missing_azure_field",
|
||||
"translation": "An Azure Blob Storage setting is missing or invalid."
|
||||
},
|
||||
{
|
||||
"id": "api.admin.test_email.body",
|
||||
"translation": "It appears your Mattermost email is setup correctly!"
|
||||
@@ -2445,8 +2449,12 @@
|
||||
"translation": "The configured bucket or container does not exist. Verify your file storage configuration and permissions."
|
||||
},
|
||||
{
|
||||
"id": "api.file.test_connection_s3_settings_nil.app_error",
|
||||
"translation": "File storage settings has unset values."
|
||||
"id": "api.file.test_connection_settings_nil.app_error",
|
||||
"translation": "File storage settings have unset values."
|
||||
},
|
||||
{
|
||||
"id": "api.file.test_connection_unsupported_driver.app_error",
|
||||
"translation": "Unsupported file storage driver \"{{.Driver}}\". Supported values are \"local\", \"amazons3\", and \"azureblob\"."
|
||||
},
|
||||
{
|
||||
"id": "api.file.upload_file.abac_denied.app_error",
|
||||
|
||||
@@ -385,6 +385,10 @@ func (c *Client4) testS3Route() clientRoute {
|
||||
return newClientRoute("file").Join("s3_test")
|
||||
}
|
||||
|
||||
func (c *Client4) testFileStoreRoute() clientRoute {
|
||||
return newClientRoute("file").Join("test")
|
||||
}
|
||||
|
||||
func (c *Client4) databaseRoute() clientRoute {
|
||||
return newClientRoute("database")
|
||||
}
|
||||
@@ -4405,6 +4409,10 @@ func (c *Client4) TestSiteURL(ctx context.Context, siteURL string) (*Response, e
|
||||
}
|
||||
|
||||
// TestS3Connection will attempt to connect to the AWS S3.
|
||||
//
|
||||
// Deprecated: use TestFileStoreConnection instead. The underlying endpoint
|
||||
// is kept for backwards compatibility but now routes through the same
|
||||
// backend-agnostic handler as TestFileStoreConnection.
|
||||
func (c *Client4) TestS3Connection(ctx context.Context, config *Config) (*Response, error) {
|
||||
r, err := c.doAPIPostJSON(ctx, c.testS3Route(), config)
|
||||
if err != nil {
|
||||
@@ -4414,6 +4422,18 @@ func (c *Client4) TestS3Connection(ctx context.Context, config *Config) (*Respon
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// TestFileStoreConnection attempts to connect to the configured file storage
|
||||
// backend (Amazon S3, Azure Blob Storage, or local), based on the FileSettings
|
||||
// in the supplied config.
|
||||
func (c *Client4) TestFileStoreConnection(ctx context.Context, config *Config) (*Response, error) {
|
||||
r, err := c.doAPIPostJSON(ctx, c.testFileStoreRoute(), config)
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// GetConfig will retrieve the server config with some sanitized items.
|
||||
func (c *Client4) GetConfig(ctx context.Context) (*Config, *Response, error) {
|
||||
r, err := c.doAPIGet(ctx, c.configRoute(), "")
|
||||
|
||||
@@ -366,8 +366,8 @@ export async function elasticsearchTest(config, success, error) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function testS3Connection(success, error) {
|
||||
const {data, error: err} = await dispatch(AdminActions.testS3Connection());
|
||||
export async function testFileStoreConnection(success, error) {
|
||||
const {data, error: err} = await dispatch(AdminActions.testFileStoreConnection());
|
||||
if (data && success) {
|
||||
success(data);
|
||||
} else if (err && error) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
removePrivateSamlCertificate,
|
||||
removePublicSamlCertificate,
|
||||
setSamlIdpCertificateFromMetadata,
|
||||
testS3Connection,
|
||||
testFileStoreConnection,
|
||||
testSiteURL,
|
||||
testSmtp,
|
||||
uploadIdpSamlCertificate,
|
||||
@@ -137,6 +137,7 @@ export {it};
|
||||
|
||||
const FILE_STORAGE_DRIVER_LOCAL = 'local';
|
||||
const FILE_STORAGE_DRIVER_S3 = 'amazons3';
|
||||
const FILE_STORAGE_DRIVER_AZURE = 'azureblob';
|
||||
const MEBIBYTE = Math.pow(1024, 2);
|
||||
|
||||
const SAML_SETTINGS_SIGNATURE_ALGORITHM_SHA1 = 'RSAwithSHA1';
|
||||
@@ -1129,7 +1130,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
type: 'dropdown',
|
||||
key: 'FileSettings.DriverName',
|
||||
label: defineMessage({id: 'admin.image.storeTitle', defaultMessage: 'File Storage System:'}),
|
||||
help_text: defineMessage({id: 'admin.image.storeDescription', defaultMessage: 'Storage system where files and image attachments are saved.\n \nSelecting "Amazon S3" enables fields to enter your Amazon credentials and bucket details.\n \nSelecting "Local File System" enables the field to specify a local file directory.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
|
||||
help_text: defineMessage({id: 'admin.image.storeDescription', defaultMessage: 'Storage system where files and image attachments are saved.\n \nSelecting "Amazon S3" enables fields to enter your Amazon credentials and bucket details.\n \nSelecting "Azure Blob Storage" enables fields to enter your Azure Storage account credentials and container details.\n \nSelecting "Local File System" enables the field to specify a local file directory.'}), // eslint-disable-line formatjs/no-multiple-whitespaces
|
||||
help_text_markdown: true,
|
||||
options: [
|
||||
{
|
||||
@@ -1140,6 +1141,10 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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'}),
|
||||
},
|
||||
],
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
|
||||
},
|
||||
@@ -1324,15 +1329,101 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_S3)),
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'FileSettings.AzureStorageAccount',
|
||||
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.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
),
|
||||
isHidden: it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'FileSettings.AzureContainer',
|
||||
label: defineMessage({id: 'admin.image.azureContainerTitle', defaultMessage: 'Azure Container:'}),
|
||||
help_text: defineMessage({id: 'admin.image.azureContainerDescription', defaultMessage: 'Name of the container in your Azure Storage account.'}),
|
||||
placeholder: defineMessage({id: 'admin.image.azureContainerExample', defaultMessage: 'E.g.: "mattermost-media"'}),
|
||||
isDisabled: it.any(
|
||||
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
|
||||
it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
),
|
||||
isHidden: it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'FileSettings.AzurePathPrefix',
|
||||
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.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
),
|
||||
isHidden: it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'FileSettings.AzureAccessKey',
|
||||
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.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
),
|
||||
isHidden: it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'FileSettings.AzureEndpoint',
|
||||
label: defineMessage({id: 'admin.image.azureEndpointTitle', defaultMessage: 'Azure Endpoint:'}),
|
||||
help_text: defineMessage({id: 'admin.image.azureEndpointDescription', defaultMessage: 'Optional host[:port] override for non-default endpoints such as Azurite, Azure Government, or other sovereign clouds. Leave empty to use the default "\'{account}\'.blob.core.windows.net" host.'}),
|
||||
placeholder: defineMessage({id: 'admin.image.azureEndpointExample', defaultMessage: 'E.g.: "azurite:10000" or leave empty'}),
|
||||
isDisabled: it.any(
|
||||
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
|
||||
it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
),
|
||||
isHidden: it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
},
|
||||
{
|
||||
type: 'bool',
|
||||
key: 'FileSettings.AzureSSL',
|
||||
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.'}),
|
||||
isDisabled: it.any(
|
||||
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
|
||||
it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
),
|
||||
isHidden: it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
key: 'FileSettings.AzureRequestTimeoutMilliseconds',
|
||||
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.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
),
|
||||
isHidden: it.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_AZURE)),
|
||||
},
|
||||
{
|
||||
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}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime
|
||||
success_message: defineMessage({id: 'admin.s3.s3Success', defaultMessage: 'Connection was successful'}),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
|
||||
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.any(
|
||||
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.FILE_STORAGE)),
|
||||
it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_LOCAL),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1360,13 +1451,17 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
type: 'dropdown',
|
||||
key: 'FileSettings.ExportDriverName',
|
||||
label: defineMessage({id: 'admin.exportStorage.exportDriverName', defaultMessage: 'Export Storage Driver:'}),
|
||||
isDisabled: true,
|
||||
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'}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1379,7 +1474,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.stateEquals('FileSettings.DedicatedExportStore', false),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -1402,7 +1497,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -1414,7 +1509,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -1426,7 +1521,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -1438,7 +1533,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -1450,7 +1545,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -1462,7 +1557,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'bool',
|
||||
@@ -1473,7 +1568,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'bool',
|
||||
@@ -1484,7 +1579,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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)),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_S3)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'bool',
|
||||
@@ -1502,7 +1597,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
),
|
||||
},
|
||||
help_text_markdown: false,
|
||||
isHidden: it.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', 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),
|
||||
@@ -1516,19 +1611,103 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
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.not(it.stateEquals('FileSettings.DriverName', FILE_STORAGE_DRIVER_S3)),
|
||||
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.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: '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),
|
||||
),
|
||||
isHidden: it.any(it.not(it.stateEquals('FileSettings.ExportDriverName', FILE_STORAGE_DRIVER_AZURE)), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'FileSettings.ExportAzureEndpoint',
|
||||
label: defineMessage({id: 'admin.image.azureEndpointTitle', defaultMessage: 'Azure Endpoint:'}),
|
||||
help_text: defineMessage({id: 'admin.image.azureEndpointDescription', defaultMessage: 'Optional host[:port] override for non-default endpoints such as Azurite, Azure Government, or other sovereign clouds. Leave empty to use the default "\'{account}\'.blob.core.windows.net" host.'}),
|
||||
placeholder: defineMessage({id: 'admin.image.azureEndpointExample', defaultMessage: 'E.g.: "azurite:10000" 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: '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.'}),
|
||||
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: '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: 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}'}), // eslint-disable-line formatjs/enforce-placeholders -- error provided at runtime
|
||||
success_message: defineMessage({id: 'admin.s3.s3Success', defaultMessage: 'Connection was successful'}),
|
||||
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.any(it.stateEquals('FileSettings.ExportDriverName', 'NONE'), it.stateEquals('FileSettings.DedicatedExportStore', false)),
|
||||
isHidden: it.stateEquals('FileSettings.DedicatedExportStore', false),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1342,6 +1342,10 @@
|
||||
"admin.file.enableMobileDownloadTitle": "Allow File Downloads on Mobile:",
|
||||
"admin.file.enableMobileUploadDesc": "When false, disables file uploads on mobile apps. If Allow File Sharing is set to true, users can still upload files from a mobile web browser.",
|
||||
"admin.file.enableMobileUploadTitle": "Allow File Uploads on Mobile:",
|
||||
"admin.filestore.connectionTest": "Test Connection",
|
||||
"admin.filestore.testFail": "Connection unsuccessful: {error}",
|
||||
"admin.filestore.testing": "Testing...",
|
||||
"admin.filestore.testSuccess": "Connection was successful",
|
||||
"admin.filter.apply": "Apply",
|
||||
"admin.filter.filters": "Filters",
|
||||
"admin.filter.reset": "Reset filters",
|
||||
@@ -1500,6 +1504,28 @@
|
||||
"admin.image.amazonS3TraceTitle": "Enable Amazon S3 Debugging:",
|
||||
"admin.image.archiveRecursionDescription": "When enabled, content of documents within ZIP files will be returned in search results. This may have an impact on server performance for large files.",
|
||||
"admin.image.archiveRecursionTitle": "Enable searching content of documents within ZIP files:",
|
||||
"admin.image.azureAccessKeyDescription": "The shared key for your Azure Storage account.",
|
||||
"admin.image.azureAccessKeyExample": "E.g.: \"9MZbtYgfq18PJ8PbRaJ5u91IH8izHvReTbcuQzMl+So=\"",
|
||||
"admin.image.azureAccessKeyTitle": "Azure Storage Account Key:",
|
||||
"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": "Optional host[:port] override for non-default endpoints such as Azurite, Azure Government, or other sovereign clouds. Leave empty to use the default \"'{account}'.blob.core.windows.net\" host.",
|
||||
"admin.image.azureEndpointExample": "E.g.: \"azurite:10000\" or leave empty",
|
||||
"admin.image.azureEndpointTitle": "Azure Endpoint:",
|
||||
"admin.image.azurePathPrefixDescription": "Optional path prefix to use for blobs in your Azure container.",
|
||||
"admin.image.azurePathPrefixExample": "E.g.: \"files/\" or leave empty",
|
||||
"admin.image.azurePathPrefixTitle": "Azure Path Prefix:",
|
||||
"admin.image.azureRequestTimeoutDescription": "Number of milliseconds to wait for a response from Azure Blob Storage before timing out.",
|
||||
"admin.image.azureRequestTimeoutExample": "E.g.: \"30000\"",
|
||||
"admin.image.azureRequestTimeoutTitle": "Azure Request Timeout (Milliseconds):",
|
||||
"admin.image.azureSSLDescription": "When false, allow insecure connections to Azure Blob Storage. Defaults to secure connections only.",
|
||||
"admin.image.azureSSLTitle": "Enable Secure Azure Blob Storage Connections:",
|
||||
"admin.image.azureStorageAccountDescription": "The name of your Azure Storage account.",
|
||||
"admin.image.azureStorageAccountExample": "E.g.: \"mattermoststorage\"",
|
||||
"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/.",
|
||||
@@ -1522,7 +1548,8 @@
|
||||
"admin.image.shareDescription": "Allow users to share public links to files and images.",
|
||||
"admin.image.shareTitle": "Enable Public File Links: ",
|
||||
"admin.image.storeAmazonS3": "Amazon S3",
|
||||
"admin.image.storeDescription": "Storage system where files and image attachments are saved.\n \nSelecting \"Amazon S3\" enables fields to enter your Amazon credentials and bucket details.\n \nSelecting \"Local File System\" enables the field to specify a local file directory.",
|
||||
"admin.image.storeAzureBlob": "Azure Blob Storage",
|
||||
"admin.image.storeDescription": "Storage system where files and image attachments are saved.\n \nSelecting \"Amazon S3\" enables fields to enter your Amazon credentials and bucket details.\n \nSelecting \"Azure Blob Storage\" enables fields to enter your Azure Storage account credentials and container details.\n \nSelecting \"Local File System\" enables the field to specify a local file directory.",
|
||||
"admin.image.storeLocal": "Local File System",
|
||||
"admin.image.storeTitle": "File Storage System:",
|
||||
"admin.info_banner.restart_required.desc": "Changing properties in this section will require a server restart before taking effect.",
|
||||
@@ -2750,10 +2777,6 @@
|
||||
"admin.reset_password.titleResetFor": "Reset password for {name}",
|
||||
"admin.reset_password.titleSwitchFor": "Switch account to Email/Password for {name}",
|
||||
"admin.revoke_token_button.delete": "Delete",
|
||||
"admin.s3.connectionS3Test": "Test Connection",
|
||||
"admin.s3.s3Fail": "Connection unsuccessful: {error}",
|
||||
"admin.s3.s3Success": "Connection was successful",
|
||||
"admin.s3.testing": "Testing...",
|
||||
"admin.saml_feature_discovery.copy": "When you connect Mattermost with your organization's single sign-on provider, users can access Mattermost without having to re-enter their credentials.",
|
||||
"admin.saml_feature_discovery.title": "Integrate SAML 2.0 with Mattermost Professional",
|
||||
"admin.saml.adminAttrDesc": "(Optional) The attribute in the SAML Assertion for designating System Admins. The users selected by the query will have access to your Mattermost server as System Admins. By default, System Admins have complete access to the Mattermost System Console.\n \nExisting members that are identified by this attribute will be promoted from member to System Admin upon next login. The next login is based upon Session lengths set in **System Console > Session Lengths**. It is highly recommend to manually demote users to members in **System Console > User Management** to ensure access is restricted immediately.\n \nNote: If this filter is removed/changed, System Admins that were promoted via this filter will be demoted to members and will not retain access to the System Console. When this filter is not in use, System Admins can be manually promoted/demoted in **System Console > User Management**.",
|
||||
|
||||
@@ -121,6 +121,11 @@ export function testSiteURL(siteURL: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use testFileStoreConnection instead. The /file/s3_test
|
||||
* endpoint is kept for backwards compatibility but is no longer
|
||||
* backend-specific.
|
||||
*/
|
||||
export function testS3Connection(config?: AdminConfig) {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.testS3Connection,
|
||||
@@ -130,6 +135,15 @@ export function testS3Connection(config?: AdminConfig) {
|
||||
});
|
||||
}
|
||||
|
||||
export function testFileStoreConnection(config?: AdminConfig) {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.testFileStoreConnection,
|
||||
params: [
|
||||
config,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function invalidateCaches() {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.invalidateCaches,
|
||||
|
||||
@@ -3625,6 +3625,11 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use testFileStoreConnection instead. The /file/s3_test
|
||||
* endpoint is kept for backwards compatibility but now routes through
|
||||
* the same backend-agnostic handler.
|
||||
*/
|
||||
testS3Connection = (config?: AdminConfig) => {
|
||||
return this.doFetch<StatusOK>(
|
||||
`${this.getBaseRoute()}/file/s3_test`,
|
||||
@@ -3632,6 +3637,13 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
testFileStoreConnection = (config?: AdminConfig) => {
|
||||
return this.doFetch<StatusOK>(
|
||||
`${this.getBaseRoute()}/file/test`,
|
||||
{method: 'post', body: JSON.stringify(config)},
|
||||
);
|
||||
};
|
||||
|
||||
invalidateCaches = () => {
|
||||
return this.doFetch<StatusOK>(
|
||||
`${this.getBaseRoute()}/caches/invalidate`,
|
||||
|
||||
Reference in New Issue
Block a user