summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/file.go35
-rw-r--r--api/post.go18
-rw-r--r--i18n/en.json20
-rw-r--r--i18n/es.json20
-rw-r--r--i18n/pt.json30
-rw-r--r--tests/test-search.md3
-rw-r--r--utils/license.go15
-rw-r--r--web/react/components/admin_console/ldap_settings.jsx6
-rw-r--r--web/react/components/admin_console/license_settings.jsx53
-rw-r--r--web/react/components/file_attachment.jsx2
-rw-r--r--web/react/components/post.jsx6
-rw-r--r--web/react/components/post_body.jsx3
-rw-r--r--web/react/components/posts_view.jsx1
-rw-r--r--web/react/components/posts_view_container.jsx10
-rw-r--r--web/react/components/search_results_item.jsx1
-rw-r--r--web/react/components/view_image_popover_bar.jsx1
-rw-r--r--web/react/stores/admin_store.jsx4
-rw-r--r--web/react/stores/analytics_store.jsx4
-rw-r--r--web/react/stores/channel_store.jsx4
-rw-r--r--web/react/stores/file_store.jsx7
-rw-r--r--web/react/stores/modal_store.jsx4
-rw-r--r--web/react/stores/post_store.jsx4
-rw-r--r--web/react/stores/search_store.jsx4
-rw-r--r--web/react/stores/suggestion_store.jsx7
-rw-r--r--web/react/stores/team_store.jsx4
-rw-r--r--web/react/stores/user_store.jsx4
-rw-r--r--web/sass-files/sass/partials/_admin-console.scss19
-rw-r--r--web/static/i18n/en.json6
-rw-r--r--web/static/i18n/es.json4
-rw-r--r--web/static/i18n/pt.json25
30 files changed, 255 insertions, 69 deletions
diff --git a/api/file.go b/api/file.go
index 0011afd5b..9150e4bfe 100644
--- a/api/file.go
+++ b/api/file.go
@@ -547,6 +547,41 @@ func writeFile(f []byte, path string) *model.AppError {
return nil
}
+func moveFile(oldPath, newPath string) *model.AppError {
+ if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
+ fileData := make(chan []byte)
+ getFileAndForget(oldPath, fileData)
+ fileBytes := <-fileData
+
+ if fileBytes == nil {
+ return model.NewLocAppError("moveFile", "api.file.move_file.get_from_s3.app_error", nil, "")
+ }
+
+ var auth aws.Auth
+ auth.AccessKey = utils.Cfg.FileSettings.AmazonS3AccessKeyId
+ auth.SecretKey = utils.Cfg.FileSettings.AmazonS3SecretAccessKey
+
+ s := s3.New(auth, awsRegion())
+ bucket := s.Bucket(utils.Cfg.FileSettings.AmazonS3Bucket)
+
+ if err := bucket.Del(oldPath); err != nil {
+ return model.NewLocAppError("moveFile", "api.file.move_file.delete_from_s3.app_error", nil, err.Error())
+ }
+
+ if err := writeFile(fileBytes, newPath); err != nil {
+ return err
+ }
+ } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
+ if err := os.Rename(utils.Cfg.FileSettings.Directory+oldPath, utils.Cfg.FileSettings.Directory+newPath); err != nil {
+ return model.NewLocAppError("moveFile", "api.file.move_file.rename.app_error", nil, err.Error())
+ }
+ } else {
+ return model.NewLocAppError("moveFile", "api.file.move_file.configured.app_error", nil, "")
+ }
+
+ return nil
+}
+
func writeFileLocally(f []byte, path string) *model.AppError {
if err := os.MkdirAll(filepath.Dir(path), 0774); err != nil {
return model.NewLocAppError("writeFile", "api.file.write_file_locally.create_dir.app_error", nil, err.Error())
diff --git a/api/post.go b/api/post.go
index e6560a8e8..cd78b16f0 100644
--- a/api/post.go
+++ b/api/post.go
@@ -1094,6 +1094,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
message.Add("post", post.ToJson())
PublishAndForget(message)
+ DeletePostFilesAndForget(c.Session.TeamId, post)
result := make(map[string]string)
result["id"] = postId
@@ -1101,6 +1102,23 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+func DeletePostFilesAndForget(teamId string, post *model.Post) {
+ go func() {
+ if len(post.Filenames) == 0 {
+ return
+ }
+
+ prefix := "teams/" + teamId + "/channels/" + post.ChannelId + "/users/" + post.UserId + "/"
+ for _, filename := range post.Filenames {
+ splitUrl := strings.Split(filename, "/")
+ oldPath := prefix + splitUrl[len(splitUrl)-2] + "/" + splitUrl[len(splitUrl)-1]
+ newPath := prefix + splitUrl[len(splitUrl)-2] + "/deleted_" + splitUrl[len(splitUrl)-1]
+ moveFile(oldPath, newPath)
+ }
+
+ }()
+}
+
func getPostsBefore(c *Context, w http.ResponseWriter, r *http.Request) {
getPostsBeforeOrAfter(c, w, r, true)
}
diff --git a/i18n/en.json b/i18n/en.json
index b7ebbb8b1..bc33fc019 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -456,8 +456,8 @@
"translation": "S3 is not supported for local storage export."
},
{
- "id": "api.export.write_file.app_error",
- "translation": "Unable to write to export file"
+ "id": "api.file.file_upload.exceeds",
+ "translation": "File exceeds max image size."
},
{
"id": "api.file.file_upload.exceeds",
@@ -508,6 +508,22 @@
"translation": "Initializing file api routes"
},
{
+ "id": "api.file.move_file.configured.app_error",
+ "translation": "File storage not configured properly. Please configure for either S3 or local server file storage."
+ },
+ {
+ "id": "api.file.move_file.delete_from_s3.app_error",
+ "translation": "Unable to delete file from S3."
+ },
+ {
+ "id": "api.file.move_file.get_from_s3.app_error",
+ "translation": "Unable to get file from S3."
+ },
+ {
+ "id": "api.file.move_file.rename.app_error",
+ "translation": "Unable to move file locally."
+ },
+ {
"id": "api.file.open_file_write_stream.configured.app_error",
"translation": "File storage not configured properly. Please configure for either S3 or local server file storage."
},
diff --git a/i18n/es.json b/i18n/es.json
index 6443d18dc..4c0c1fd03 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -456,8 +456,8 @@
"translation": "S3 no está soportado para exportar al almacenamiento local."
},
{
- "id": "api.export.write_file.app_error",
- "translation": "No se puede escribir al archivo a exportar"
+ "id": "api.file.file_upload.exceeds",
+ "translation": "El archivo excede el tamaño máximo para una imagen."
},
{
"id": "api.file.file_upload.exceeds",
@@ -508,6 +508,22 @@
"translation": "Inicializando rutas del API para los archivos"
},
{
+ "id": "api.file.move_file.configured.app_error",
+ "translation": "No ha sido configurado apropiadamente el almacenamiento. Por favor configuralo para utilizar ya sea S3 o almacenamiento local."
+ },
+ {
+ "id": "api.file.move_file.delete_from_s3.app_error",
+ "translation": "No se pudo eliminar el archivo del S3."
+ },
+ {
+ "id": "api.file.move_file.get_from_s3.app_error",
+ "translation": "No se pudo obtener el archivo desde el S3."
+ },
+ {
+ "id": "api.file.move_file.rename.app_error",
+ "translation": "No se pudo mover el archivo localmente."
+ },
+ {
"id": "api.file.open_file_write_stream.configured.app_error",
"translation": "El almacenamiento de archivos no ha sido configurado apropiadamente. Por favor configuralo ya sea para S3 o para almacenamiento en el servidor local."
},
diff --git a/i18n/pt.json b/i18n/pt.json
index e01db6b22..8e45d5eaf 100644
--- a/i18n/pt.json
+++ b/i18n/pt.json
@@ -2408,6 +2408,10 @@
"translation": "Pingando banco de dados sql %v"
},
{
+ "id": "store.sql.read_replicas_not_licensed.critical",
+ "translation": "A funcionalidade de mais de uma replica está desabilitada pela licença atual. Entre em contato com o administrador do sistema sobre como atualizar sua licença de empresa."
+ },
+ {
"id": "store.sql.remove_index.critical",
"translation": "Falha ao remover o índice %v"
},
@@ -2648,6 +2652,10 @@
"translation": "Encontramos um erro ao atualizar o membro do canal"
},
{
+ "id": "store.sql_command.analytics_command_count.app_error",
+ "translation": "Não foi possível contar os comandos"
+ },
+ {
"id": "store.sql_command.save.delete.app_error",
"translation": "Não foi possível deletar o comando"
},
@@ -2676,10 +2684,6 @@
"translation": "Não foi possível atualizar o comando"
},
{
- "id": "store.sql_command.analytics_command_count.app_error",
- "translation": "Não foi possível contar os comandos"
- },
- {
"id": "store.sql_license.get.app_error",
"translation": "Encontramos um erro ao obter a licença"
},
@@ -2900,6 +2904,10 @@
"translation": "Não foi possível atualizar a preferência"
},
{
+ "id": "store.sql_session.analytics_session_count.app_error",
+ "translation": "Não foi possível contar a sessão"
+ },
+ {
"id": "store.sql_session.cleanup_expired_sessions.app_error",
"translation": "Encontramos um erro enquanto deletava a sessão expirada do usuário"
},
@@ -2952,10 +2960,6 @@
"translation": "Não foi possível atualizar as funções"
},
{
- "id": "store.sql_session.analytics_session_count.app_error",
- "translation": "Não foi possível contar a sessão"
- },
- {
"id": "store.sql_system.get.app_error",
"translation": "Encontramos um erro ao procurar as propriedades de sistema"
},
@@ -2968,6 +2972,10 @@
"translation": "Encontramos um erro ao atualizar as propriedades de sistema"
},
{
+ "id": "store.sql_team.analytics_team_count.app_error",
+ "translation": "Não foi possível contar as equipes"
+ },
+ {
"id": "store.sql_team.get.find.app_error",
"translation": "Não foi possível encontrar a equipe existente"
},
@@ -3036,10 +3044,6 @@
"translation": "Não foi possível atualizar o nome da equipe"
},
{
- "id": "store.sql_team.analytics_team_count.app_error",
- "translation": "Não foi possível contar as equipes"
- },
- {
"id": "store.sql_user.analytics_unique_user_count.app_error",
"translation": "Não foi possível obter o número de usuários únicos"
},
@@ -3587,4 +3591,4 @@
"id": "web.watcher_fail.error",
"translation": "Falha ao adicionar diretório observador %v"
}
-]
+] \ No newline at end of file
diff --git a/tests/test-search.md b/tests/test-search.md
index 0f0ba1153..828828c25 100644
--- a/tests/test-search.md
+++ b/tests/test-search.md
@@ -40,4 +40,5 @@ Click on the linked hashtags below, and confirm that the search results match th
#### Markdown surrounding a hashtag:
-*#markdown-hashtag*
+*#markdown* **#markdown** ~~#markdown~~
+##### #markdown
diff --git a/utils/license.go b/utils/license.go
index b773a163e..5c975aec2 100644
--- a/utils/license.go
+++ b/utils/license.go
@@ -22,15 +22,14 @@ var IsLicensed bool = false
var License *model.License = &model.License{}
var ClientLicense map[string]string = make(map[string]string)
-// test public key
var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3/k3Al9q1Xe+xngQ/yGn
-0suaJopea3Cpf6NjIHdO8sYTwLlxqt0Mdb+qBR9LbCjZfcNmqc5mZONvsyCEoN/5
-VoLdlv1m9ao2BSAWphUxE2CPdUWdLOsDbQWliSc5//UhiYeR+67Xxon0Hg0LKXF6
-PumRIWQenRHJWqlUQZ147e7/1v9ySVRZksKpvlmMDzgq+kCH/uyM1uVP3z7YXhlN
-K7vSSQYbt4cghvWQxDZFwpLlsChoY+mmzClgq+Yv6FLhj4/lk94twdOZau/AeZFJ
-NxpC+5KFhU+xSeeklNqwCgnlOyZ7qSTxmdJHb+60SwuYnnGIYzLJhY4LYDr4J+KR
-1wIDAQAB
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r
+tSyzyxrXkJjsFUf0Ke7bm/TLtIggRdqOcUF3XEWqQk5RGD5vuq7Rlg1zZqMEBk8N
+EZeRhkxyaZW8pLjxwuBUOnXfJew31+gsTNdKZzRjrvPumKr3EtkleuoxNdoatu4E
+HrKmR/4Yi71EqAvkhk7ZjQFuF0osSWJMEEGGCSUYQnTEqUzcZSh1BhVpkIkeu8Kk
+1wCtptODixvEujgqVe+SrE3UlZjBmPjC/CL+3cYmufpSNgcEJm2mwsdaXp2OPpfn
+a0v85XL6i9ote2P+fLZ3wX9EoioHzgdgB7arOxY50QRJO7OyCqpKFKv6lRWTXuSt
+hwIDAQAB
-----END PUBLIC KEY-----`)
func LoadLicense(licenseBytes []byte) {
diff --git a/web/react/components/admin_console/ldap_settings.jsx b/web/react/components/admin_console/ldap_settings.jsx
index 535c264dd..4cd19c886 100644
--- a/web/react/components/admin_console/ldap_settings.jsx
+++ b/web/react/components/admin_console/ldap_settings.jsx
@@ -20,7 +20,7 @@ var holders = defineMessages({
},
baseEx: {
id: 'admin.ldap.baseEx',
- defaultMessage: 'Ex "dc=mydomain,dc=com"'
+ defaultMessage: 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"'
},
firstnameAttrEx: {
id: 'admin.ldap.firstnameAttrEx',
@@ -32,7 +32,7 @@ var holders = defineMessages({
},
emailAttrEx: {
id: 'admin.ldap.emailAttrEx',
- defaultMessage: 'Ex "mail"'
+ defaultMessage: 'Ex "mail" or "userPrincipalName"'
},
usernameAttrEx: {
id: 'admin.ldap.usernameAttrEx',
@@ -581,4 +581,4 @@ LdapSettings.propTypes = {
config: React.PropTypes.object
};
-export default injectIntl(LdapSettings); \ No newline at end of file
+export default injectIntl(LdapSettings);
diff --git a/web/react/components/admin_console/license_settings.jsx b/web/react/components/admin_console/license_settings.jsx
index d4dfa13f2..9d2ec8030 100644
--- a/web/react/components/admin_console/license_settings.jsx
+++ b/web/react/components/admin_console/license_settings.jsx
@@ -27,6 +27,7 @@ class LicenseSettings extends React.Component {
this.state = {
fileSelected: false,
+ fileName: null,
serverError: null
};
}
@@ -34,7 +35,7 @@ class LicenseSettings extends React.Component {
handleChange() {
const element = $(ReactDOM.findDOMNode(this.refs.fileInput));
if (element.prop('files').length > 0) {
- this.setState({fileSelected: true});
+ this.setState({fileSelected: true, fileName: element.prop('files')[0].name});
}
}
@@ -56,13 +57,13 @@ class LicenseSettings extends React.Component {
() => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
- this.setState({serverError: null});
+ this.setState({fileSelected: false, fileName: null, serverError: null});
window.location.reload(true);
},
(error) => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
- this.setState({serverError: error.message});
+ this.setState({fileSelected: false, fileName: null, serverError: error.message});
}
);
}
@@ -75,12 +76,12 @@ class LicenseSettings extends React.Component {
Client.removeLicenseFile(
() => {
$('#remove-button').button('reset');
- this.setState({serverError: null});
+ this.setState({fileSelected: false, fileName: null, serverError: null});
window.location.reload(true);
},
(error) => {
$('#remove-button').button('reset');
- this.setState({serverError: error.message});
+ this.setState({fileSelected: false, fileName: null, serverError: error.message});
}
);
}
@@ -172,17 +173,36 @@ class LicenseSettings extends React.Component {
/>
);
+ let fileName;
+ if (this.state.fileName) {
+ fileName = this.state.fileName;
+ } else {
+ fileName = (
+ <FormattedMessage
+ id='admin.license.noFile'
+ defaultMessage='No file uploaded'
+ />
+ );
+ }
+
licenseKey = (
<div className='col-sm-8'>
- <input
- className='pull-left'
- ref='fileInput'
- type='file'
- accept='.mattermost-license'
- onChange={this.handleChange}
- />
+ <div className='file__upload'>
+ <button className='btn btn-default'>
+ <FormattedMessage
+ id='admin.license.choose'
+ defaultMessage='Choose File'
+ />
+ </button>
+ <input
+ ref='fileInput'
+ type='file'
+ accept='.mattermost-license'
+ onChange={this.handleChange}
+ />
+ </div>
<button
- className={btnClass + ' pull-left'}
+ className={btnClass}
disabled={!this.state.fileSelected}
onClick={this.handleSubmit}
id='upload-button'
@@ -193,11 +213,12 @@ class LicenseSettings extends React.Component {
defaultMessage='Upload'
/>
</button>
- <br/>
- <br/>
+ <div className='help-text no-margin'>
+ {fileName}
+ </div>
<br/>
{serverError}
- <p className='help-text'>
+ <p className='help-text no-margin'>
<FormattedHTMLMessage
id='admin.license.uploadDesc'
defaultMessage='Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href="http://mattermost.com" target="_blank">Visit us online</a> to learn more about the benefits of Enterprise Edition or to purchase a key.'
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index c719c6c7d..2f6067b86 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -185,6 +185,7 @@ class FileAttachment extends React.Component {
data-toggle='tooltip'
title={this.props.intl.formatMessage(holders.download) + ' \"' + filenameString + '\"'}
className='post-image__name'
+ target='_blank'
>
{trimmedFilename}
</a>
@@ -193,6 +194,7 @@ class FileAttachment extends React.Component {
href={fileUrl}
download={filenameString}
className='post-image__download'
+ target='_blank'
>
<span
className='fa fa-download'
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 889d4311e..57e919e45 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -98,7 +98,7 @@ export default class Post extends React.Component {
return true;
}
- if (nextProps.hasProfiles !== this.props.hasProfiles) {
+ if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
return true;
}
@@ -226,7 +226,6 @@ export default class Post extends React.Component {
posts={posts}
handleCommentClick={this.handleCommentClick}
retryPost={this.retryPost}
- hasProfiles={this.props.hasProfiles}
/>
</div>
</div>
@@ -246,6 +245,5 @@ Post.propTypes = {
hideProfilePic: React.PropTypes.bool,
isLastComment: React.PropTypes.bool,
shouldHighlight: React.PropTypes.bool,
- displayNameType: React.PropTypes.string,
- hasProfiles: React.PropTypes.bool
+ displayNameType: React.PropTypes.string
};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 70cf86748..854cb095a 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -215,8 +215,7 @@ PostBody.propTypes = {
post: React.PropTypes.object.isRequired,
parentPost: React.PropTypes.object,
retryPost: React.PropTypes.func.isRequired,
- handleCommentClick: React.PropTypes.func.isRequired,
- hasProfiles: React.PropTypes.bool
+ handleCommentClick: React.PropTypes.func.isRequired
};
export default injectIntl(PostBody);
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index 9a1673483..c2c739e9a 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -250,7 +250,6 @@ export default class PostsView extends React.Component {
shouldHighlight={shouldHighlight}
onClick={() => EventHelpers.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
displayNameType={this.state.displayNameType}
- hasProfiles={profiles && Object.keys(profiles).length > 1}
user={profile}
/>
);
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 92d658b55..976e03fab 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -149,11 +149,15 @@ export default class PostsViewContainer extends React.Component {
}
}
shouldComponentUpdate(nextProps, nextState) {
- if (Utils.areObjectsEqual(this.state, nextState)) {
- return false;
+ if (!Utils.areObjectsEqual(this.state, nextState)) {
+ return true;
}
- return true;
+ if (!Utils.areObjectsEqual(this.props, nextProps)) {
+ return true;
+ }
+
+ return false;
}
render() {
const postLists = this.state.postLists;
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 05292b7b3..5ab864b7c 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -123,6 +123,7 @@ export default class SearchResultsItem extends React.Component {
</ul>
<div className='search-item-snippet'>
<span
+ onClick={TextFormatting.handleClick}
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, formattingOptions)}}
/>
</div>
diff --git a/web/react/components/view_image_popover_bar.jsx b/web/react/components/view_image_popover_bar.jsx
index 819df76d8..18be5a3c5 100644
--- a/web/react/components/view_image_popover_bar.jsx
+++ b/web/react/components/view_image_popover_bar.jsx
@@ -51,6 +51,7 @@ export default class ViewImagePopoverBar extends React.Component {
href={this.props.fileURL}
download={this.props.filename}
className='text'
+ target='_blank'
>
<FormattedMessage
id='view_image_popover.download'
diff --git a/web/react/stores/admin_store.jsx b/web/react/stores/admin_store.jsx
index eb3254cfe..5c911e94b 100644
--- a/web/react/stores/admin_store.jsx
+++ b/web/react/stores/admin_store.jsx
@@ -156,3 +156,7 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
});
export default AdminStore;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.AdminStore = AdminStore;
+}
diff --git a/web/react/stores/analytics_store.jsx b/web/react/stores/analytics_store.jsx
index ec827f6d7..0ad989206 100644
--- a/web/react/stores/analytics_store.jsx
+++ b/web/react/stores/analytics_store.jsx
@@ -83,3 +83,7 @@ AnalyticsStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default AnalyticsStore;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.AnalyticsStore = AnalyticsStore;
+}
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 60cb10de7..eac24b071 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -350,3 +350,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default ChannelStore;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.ChannelStore = ChannelStore;
+}
diff --git a/web/react/stores/file_store.jsx b/web/react/stores/file_store.jsx
index 6d7e0f354..c1fd0ef74 100644
--- a/web/react/stores/file_store.jsx
+++ b/web/react/stores/file_store.jsx
@@ -57,4 +57,9 @@ class FileStore extends EventEmitter {
}
}
-export default new FileStore();
+const instance = new FileStore();
+export default instance;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.FileStore = instance;
+}
diff --git a/web/react/stores/modal_store.jsx b/web/react/stores/modal_store.jsx
index 5ea38030b..1819fffc0 100644
--- a/web/react/stores/modal_store.jsx
+++ b/web/react/stores/modal_store.jsx
@@ -45,3 +45,7 @@ class ModalStoreClass extends EventEmitter {
const ModalStore = new ModalStoreClass();
export default ModalStore;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.ModalStore = ModalStore;
+}
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index a6dfcd46f..5cc3f300d 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -608,3 +608,7 @@ function isPostListNull(pl) {
return false;
}
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.PostStore = PostStore;
+}
diff --git a/web/react/stores/search_store.jsx b/web/react/stores/search_store.jsx
index 549f355ef..96071665c 100644
--- a/web/react/stores/search_store.jsx
+++ b/web/react/stores/search_store.jsx
@@ -135,3 +135,7 @@ SearchStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default SearchStore;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.SearchStore = SearchStore;
+}
diff --git a/web/react/stores/suggestion_store.jsx b/web/react/stores/suggestion_store.jsx
index efd2b76ed..487bae843 100644
--- a/web/react/stores/suggestion_store.jsx
+++ b/web/react/stores/suggestion_store.jsx
@@ -258,4 +258,9 @@ class SuggestionStore extends EventEmitter {
}
}
-export default new SuggestionStore();
+const instance = new SuggestionStore();
+export default instance;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.SuggestionStore = instance;
+}
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index 7a1a2ef42..493d6bc4d 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -126,3 +126,7 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default TeamStore;
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.TeamStore = TeamStore;
+}
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 75a87d424..9fcd2440e 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -325,3 +325,7 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
});
export {UserStore as default};
+
+if (window.mm_config.EnableDeveloper === 'true') {
+ window.UserStore = UserStore;
+}
diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss
index f782da36b..76081710f 100644
--- a/web/sass-files/sass/partials/_admin-console.scss
+++ b/web/sass-files/sass/partials/_admin-console.scss
@@ -145,12 +145,27 @@
.form-group {
margin-bottom: 25px;
}
+ .file__upload {
+ position: relative;
+ margin: 0 10px 10px 0;
+ display: inline-block;
+ input {
+ position: absolute;
+ @include opacity(0);
+ width: 100%;
+ height: 100%;
+ z-index: 5;
+ top: 0;
+ left: 0;
+ }
+ }
.help-text {
+ &.no-margin {
+ margin: 0;
+ }
ul, ol {
padding-left: 23px;
}
- }
- .help-text {
margin: 10px 0 0 15px;
color: #777;
.help-link {
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index e3d331593..feea91aad 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -153,14 +153,14 @@
"admin.ldap.bannerDesc": "If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.",
"admin.ldap.bannerHeading": "Note:",
"admin.ldap.baseDesc": "The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.",
- "admin.ldap.baseEx": "Ex \"dc=mydomain,dc=com\"",
+ "admin.ldap.baseEx": "Ex \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "BaseDN:",
"admin.ldap.bindPwdDesc": "Password of the user given in \"Bind Username\".",
"admin.ldap.bindPwdTitle": "Bind Password:",
"admin.ldap.bindUserDesc": "The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.",
"admin.ldap.bindUserTitle": "Bind Username:",
"admin.ldap.emailAttrDesc": "The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.",
- "admin.ldap.emailAttrEx": "Ex \"mail\"",
+ "admin.ldap.emailAttrEx": "Ex \"mail\" or \"userPrincipalName\"",
"admin.ldap.emailAttrTitle": "Email Attribute:",
"admin.ldap.enableDesc": "When true, Mattermost allows login using LDAP",
"admin.ldap.enableTitle": "Enable Login With LDAP:",
@@ -192,6 +192,8 @@
"admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Username Attribute:",
"admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.",
+ "admin.license.noFile": "No file uploaded",
+ "admin.license.chooseFile": "Choose File",
"admin.license.edition": "Edition: ",
"admin.license.enterpriseEdition": "Mattermost Enterprise Edition. Designed for enterprise-scale communication.",
"admin.license.enterpriseType": "<div><p>This compiled release of Mattermost platform is provided under a <a href=\"http://mattermost.com\" target=\"_blank\">commercial license</a> from Mattermost, Inc. based on your subscription level and is subject to the <a href=\"{terms}\" target=\"_blank\">Terms of Service.</a></p><p>Your subscription details are as follows:</p>Name: {name}<br />Company or organization name: {company}<br/>Number of users: {users}<br/>License issued: {issued}<br/>Start date of license: {start}<br/>Expiry date of license: {expires}<br/>LDAP: {ldap}<br/></div>",
diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json
index f9bb58ef3..2a7eaae16 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -153,14 +153,14 @@
"admin.ldap.bannerDesc": "Si el atributo de un usuario cambia en el servidor LDAP será actualizado la próxima vez que el usuario ingrese sus credenciales para iniciar sesión en Mattermost. Esto incluye si un usuario se inactiva o se remueve en el servidor LDAP. Sincronización con servidores LDAP está planificado para futuras versiones.",
"admin.ldap.bannerHeading": "Nota:",
"admin.ldap.baseDesc": "El DN Base es el Nombre Distinguido de la ubicación donde Mattermost debe comenzar a buscar a los usuarios en el árbol del LDAP.",
- "admin.ldap.baseEx": "Ex \"dc=midominio,dc=com\"",
+ "admin.ldap.baseEx": "Ej \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "DN Base:",
"admin.ldap.bindPwdDesc": "Contraseña del usuario asignado en \"Usuario de Enlace\".",
"admin.ldap.bindPwdTitle": "Contraseña de Enlace:",
"admin.ldap.bindUserDesc": "El usuario que realizará las busquedas LDAP. Normalmente este debería ser una cuenta específicamente creada para ser utilizada por Mattermost. Debería contat con acceso limitado para leer la porción del árbol LDAP especificada en el campo DN Base.",
"admin.ldap.bindUserTitle": "Usuario de Enlace:",
"admin.ldap.emailAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar la dirección de correo electrónico de los usuarios en Mattermost.",
- "admin.ldap.emailAttrEx": "Ej \"mail\"",
+ "admin.ldap.emailAttrEx": "Ej \"mail\" o \"userPrincipalName\"",
"admin.ldap.emailAttrTitle": "Atributo de Correo Electrónico:",
"admin.ldap.enableDesc": "Cuando es verdadero, Mattermost permite realizar inicio de sesión utilizando LDAP",
"admin.ldap.enableTitle": "Habilitar inicio de sesión con LDAP:",
diff --git a/web/static/i18n/pt.json b/web/static/i18n/pt.json
index 3bed12ebf..b9b8f4c07 100644
--- a/web/static/i18n/pt.json
+++ b/web/static/i18n/pt.json
@@ -153,14 +153,14 @@
"admin.ldap.bannerDesc": "Se um atributo de usuário de mudar no servidor LDAP, ele será atualizado na próxima vez que o usuário inserir suas credenciais para iniciar sessão no Mattermost. Isso inclui se um usuário estiver inativo ou removido de um servidor LDAP. Sincronização com servidores LDAP está prevista para um lançamento futuro.",
"admin.ldap.bannerHeading": "Nota:",
"admin.ldap.baseDesc": "Base DN é o nome distinto do local onde Mattermost deve começar sua busca para os usuários na árvore LDAP.",
- "admin.ldap.baseEx": "Ex \"dc=mydomain,dc=com\"",
+ "admin.ldap.baseEx": "Ex \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "BaseDN:",
"admin.ldap.bindPwdDesc": "Senha do usuário fornecido em \"Bind Username\".",
"admin.ldap.bindPwdTitle": "Vincular Senha:",
"admin.ldap.bindUserDesc": "O nome de usuário usado para realizar a pesquisa LDAP. Isso deve ser tipicamente uma conta criada especificamente para uso do Mattermost. Deve ter acesso limitado a ler a parte da árvore LDAP especificado no campo BaseDN.",
"admin.ldap.bindUserTitle": "Bind Username:",
"admin.ldap.emailAttrDesc": "O atributo no servidor LDAP que será usado para preencher os endereços de e-mail de usuários no Mattermost.",
- "admin.ldap.emailAttrEx": "Ex \"mail\"",
+ "admin.ldap.emailAttrEx": "Ex \"mail\" ou \"userPrincipalName\"",
"admin.ldap.emailAttrTitle": "Atributo de E-mail:",
"admin.ldap.enableDesc": "Quando verdadeiro, Mattermost permite login utilizando LDAP",
"admin.ldap.enableTitle": "Ativar Login With LDAP:",
@@ -278,6 +278,9 @@
"admin.service.attemptTitle": "Máxima Tentativas de Login:",
"admin.service.cmdsDesc": "Quando verdadeiro, comandos slash criados por usuários serão permitidos.",
"admin.service.cmdsTitle": "Ativar Comandos Slash: ",
+ "admin.service.corsDescription": "Ativar requisição de origem HTTP Cross dos domínios especificados (separados por espaço). Usar \"*\" se você quiser permitir CORS de qualquer domínio ou deixe em branco para desativar.",
+ "admin.service.corsEx": "http://example.com https://example.com",
+ "admin.service.corsTitle": "Permitir Requisição Cross-origin de:",
"admin.service.developerDesc": "(Opção dos desenvolvedores) Quando verdadeira, a informação extra em torno dos erros será exibida na UI.",
"admin.service.developerTitle": "Ativar o Modo Desenvolvedor: ",
"admin.service.false": "falso",
@@ -559,6 +562,7 @@
"channel_loader.wrote": " escreveu: ",
"channel_members_modal.addNew": " Adicionar Novos Membros",
"channel_members_modal.close": "Fechar",
+ "channel_members_modal.remove": "Remover",
"channel_memebers_modal.members": " Membros",
"channel_modal.cancel": "Cancelar",
"channel_modal.channel": "Canal",
@@ -590,6 +594,7 @@
"choose_auth_page.find": "Encontrar minhas equipes",
"choose_auth_page.gitlabCreate": "Criar uma equipe com uma conta GitLab",
"choose_auth_page.googleCreate": "Criar nova equipe com a Conta do Google Apps",
+ "choose_auth_page.ldapCreate": "Criar uma nova equipe com uma conta LDAP",
"choose_auth_page.noSignup": "Nenhum método de inscrição configurado, por favor contate seu administrador do sistema.",
"claim.account.noEmail": "Nenhum email específicado",
"claim.email_to_sso.enterPwd": "Entre a senha para o sua conta {team} {site}",
@@ -666,8 +671,8 @@
"file_upload.filesAbove": "Arquivos acima {max}MB não podem ser enviados: {filenames}",
"file_upload.limited": "Limite máximo de uploads de {count} arquivos. Por favor use um post adicional para mais arquivos.",
"file_upload.pasted": "Imagem Colada em ",
- "filtered_user_list.count": "{count, number} {count, plural, one {Membro} other {Membros}}",
- "filtered_user_list.countTotal": "{count, number} {count, plural, one {Membro} other {Membros}} de {total} Total",
+ "filtered_user_list.count": "{count, number} {count, plural, one {membro} other {membros}}",
+ "filtered_user_list.countTotal": "{count, number} {count, plural, one {membro} other {membros}} de {total} Total",
"filtered_user_list.search": "Procurar membros",
"find_team.email": "E-mail",
"find_team.findDescription": "Foi enviado um e-mail com links para todas as equipes do qual você é membro.",
@@ -699,6 +704,7 @@
"get_link.copy": "Copiar Link",
"get_post_link_modal.help": "O link abaixo permite que usuários autorizados possam ver seus posts.",
"get_post_link_modal.title": "Copiar Permalink",
+ "get_team_invite_link_modal.help": "Enviar o link abaixo para sua equipe de trabalho para que eles se inscrevam no site da sua equipe. O Link de Convite de Equipe como ele não muda pode ser compartilhado com várias pessoas ao menos que seja re-gerado em Configurações de Equipe pelo Administrador de Equipe.",
"get_team_invite_link_modal.helpDisabled": "Criação de usuários está desabilitada para sua equipe. Por favor peça ao administrador de equipe por detalhes.",
"get_team_invite_link_modal.title": "Link para Convite de Equipe",
"intro_messages.DM": "Este é o início do seu histórico de mensagens diretas com {teammate}.<br />Mensagens diretas e arquivos compartilhados aqui não são mostrados para pessoas de fora desta área.",
@@ -732,6 +738,11 @@
"invite_member.send2": "Enviar Convites",
"invite_member.sending": " Enviando",
"invite_member.teamInviteLink": "Você também pode convidar pessoas usando o {link}",
+ "ldap_signup.find": "Encontrar minhas equipes",
+ "ldap_signup.ldap": "Criar uma equipe com uma conta LDAP",
+ "ldap_signup.length_error": "O nome deve ser de 3 ou mais caracteres até um máximo de 15",
+ "ldap_signup.teamName": "Entre o nome da nova equipe",
+ "ldap_signup.team_error": "Por favor entre o nome da equipe",
"loading_screen.loading": "Carregando",
"login.changed": " Método de login alterada com sucesso",
"login.create": "Crie um agora",
@@ -767,6 +778,7 @@
"login_username.verifyEmailError": "Por favor verifique seu endereço de email. Verifique por um email na sua caixa de entrada.",
"member_item.makeAdmin": "Tornar Admin",
"member_item.member": "Membro",
+ "member_list.noUsersAdd": "Nenhum usuário para adicionar.",
"members_popover.msg": "Mensagem",
"members_popover.title": "Membros",
"more_channels.close": "Fechar",
@@ -961,6 +973,7 @@
"sso_signup.team_error": "Por favor entre o nome da equipe",
"suggestion.mention.all": "Notificar todo mundo na equipe",
"suggestion.mention.channel": "Notifica todos no canal",
+ "suggestion.search.private": "Grupos Privados",
"suggestion.search.public": "Canais Públicos",
"team_export_tab.download": "download",
"team_export_tab.export": "Exportar",
@@ -1053,9 +1066,9 @@
"textbox.help": "Ajuda",
"textbox.inlinecode": "`código`",
"textbox.italic": "_itálico_",
- "textbox.preformatted": "```pre-formatado```",
+ "textbox.preformatted": "```pré-formatado```",
"textbox.preview": "Pré-visualização",
- "textbox.quote": ">citado",
+ "textbox.quote": ">citar",
"textbox.strike": "tachado",
"tutorial_intro.allSet": "Está tudo pronto",
"tutorial_intro.end": "Clique em “Próximo” para entrar Town Square. Este é o primeiro canal que sua equipe de trabalho vê quando eles se inscrevem. Use para postar atualizações que todos precisam saber.",