summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--api/admin.go2
-rw-r--r--api/file.go4
-rw-r--r--api/templates/email_change_verify_body.html2
-rw-r--r--config/config.json6
-rw-r--r--doc/developer/Code-Contribution-Guidelines.md6
-rw-r--r--doc/developer/Setup.md4
-rw-r--r--doc/install/Configuration-Settings.md302
-rw-r--r--doc/install/SMTP-Email-Setup.md14
-rw-r--r--docker/1.1/config_docker.json6
-rw-r--r--docker/dev/config_docker.json6
-rw-r--r--docker/local/config_docker.json6
-rw-r--r--mattermost.go38
-rw-r--r--model/config.go13
-rw-r--r--store/sql_user_store.go21
-rw-r--r--store/sql_user_store_test.go20
-rw-r--r--store/store.go1
-rw-r--r--utils/config.go2
-rw-r--r--utils/diagnostic.go19
-rw-r--r--web/react/components/admin_console/privacy_settings.jsx34
-rw-r--r--web/react/components/admin_console/service_settings.jsx37
-rw-r--r--web/react/components/channel_loader.jsx6
-rw-r--r--web/react/components/create_comment.jsx40
-rw-r--r--web/react/components/get_link_modal.jsx1
-rw-r--r--web/react/components/setting_item_max.jsx11
-rw-r--r--web/react/components/settings_sidebar.jsx2
-rw-r--r--web/react/components/signup_user_complete.jsx2
-rw-r--r--web/react/components/textbox.jsx2
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx3
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx2
-rw-r--r--web/react/components/view_image.jsx3
-rw-r--r--web/react/utils/constants.jsx2
-rw-r--r--web/react/utils/utils.jsx7
-rw-r--r--web/sass-files/sass/partials/_base.scss5
-rw-r--r--web/sass-files/sass/partials/_headers.scss5
-rw-r--r--web/sass-files/sass/partials/_markdown.scss9
-rw-r--r--web/sass-files/sass/partials/_modal.scss3
-rw-r--r--web/sass-files/sass/partials/_post.scss8
-rw-r--r--web/sass-files/sass/partials/_responsive.scss5
-rw-r--r--web/sass-files/sass/partials/_settings.scss1
-rw-r--r--web/sass-files/sass/partials/_sidebar--right.scss1
-rw-r--r--web/templates/channel.html2
42 files changed, 533 insertions, 132 deletions
diff --git a/Makefile b/Makefile
index 12b209c84..103370288 100644
--- a/Makefile
+++ b/Makefile
@@ -116,7 +116,7 @@ travis:
rm $(DIST_PATH)/web/templates/*.bak
mv doc/README.md doc/index.md
- mkdocs build
+ mkdocs build --strict
cp -r documentation-html $(DIST_PATH)/documentation-html
tar -C dist -czf $(DIST_PATH).tar.gz mattermost
diff --git a/api/admin.go b/api/admin.go
index 2167868e0..cd1e5d2de 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -104,6 +104,8 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ cfg.SetDefaults()
+
if err := cfg.IsValid(); err != nil {
c.Err = err
return
diff --git a/api/file.go b/api/file.go
index 9ebcd821b..429347596 100644
--- a/api/file.go
+++ b/api/file.go
@@ -408,11 +408,11 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", strconv.Itoa(len(f)))
w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer
- // attach extra headers to trigger a download on IE and Edge
+ // attach extra headers to trigger a download on IE, Edge, and Safari
ua := user_agent.New(r.UserAgent())
bname, _ := ua.Browser()
- if bname == "Edge" || bname == "Internet Explorer" {
+ if bname == "Edge" || bname == "Internet Explorer" || bname == "Safari" {
// trim off anything before the final / so we just get the file's name
parts := strings.Split(filename, "/")
diff --git a/api/templates/email_change_verify_body.html b/api/templates/email_change_verify_body.html
index 1e1bcc22d..a9b2a0741 100644
--- a/api/templates/email_change_verify_body.html
+++ b/api/templates/email_change_verify_body.html
@@ -9,7 +9,7 @@
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 20px 20px 10px; text-align:left;">
- <img src="{{.Props.SiteURL}}/static/images/{{.ClientProps.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
+ <img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
<tr>
diff --git a/config/config.json b/config/config.json
index 919737da7..8ef151350 100644
--- a/config/config.json
+++ b/config/config.json
@@ -8,7 +8,8 @@
"EnableIncomingWebhooks": true,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
- "EnableTesting": false
+ "EnableTesting": false,
+ "EnableSecurityFixAlert": true
},
"TeamSettings": {
"SiteName": "Mattermost",
@@ -77,8 +78,7 @@
},
"PrivacySettings": {
"ShowEmailAddress": true,
- "ShowFullName": true,
- "EnableSecurityFixAlert": true
+ "ShowFullName": true
},
"GitLabSettings": {
"Enable": false,
diff --git a/doc/developer/Code-Contribution-Guidelines.md b/doc/developer/Code-Contribution-Guidelines.md
index 80676f107..453c154f9 100644
--- a/doc/developer/Code-Contribution-Guidelines.md
+++ b/doc/developer/Code-Contribution-Guidelines.md
@@ -5,7 +5,6 @@ Thank you for your interest in contributing to Mattermost. This guide provides a
## Choose a Ticket
1. Review the list of [Good First Contribution](https://mattermost.atlassian.net/issues/?filter=10206) tickets listed in Jira.
-
2. These projects are intended to be a straight forward first pull requests from new contributors.
If you don't find something appropriate for your interests, please see the full list of tickets [Accepting Pull Requests](https://mattermost.atlassian.net/issues/?filter=10101).
@@ -13,7 +12,7 @@ If you don't find something appropriate for your interests, please see the full
## Install Mattermost and set up a Fork
-1. Follow [developer setup instructions](https://github.com/mattermost/platform/blob/master/doc/install/dev-setup.md) to install Mattermost.
+1. Follow [developer setup instructions](https://github.com/mattermost/platform/blob/master/doc/developer/Setup.md) to install Mattermost.
2. Create a branch with <branch name> set to the ID of the ticket you're working on, for example ```PLT-394```, using command:
@@ -29,6 +28,9 @@ git checkout -b <branch name>
2. Please make sure to thoroughly test your change before submitting a pull request.
+ Please review the ["Fast, Obvious, Forgiving" experience design principles](http://www.mattermost.org/design-principles/) for Mattermost and check that your feature meets the criteria. Also, for any changes to user interface or help text, please read the changes out loud, as a quick and easy way to catch any inconsitencies.
+
+
## Submitting a Pull Request
1. Please add yourself to the Mattermost [approved contributor list](https://docs.google.com/spreadsheets/d/1NTCeG-iL_VS9bFqtmHSfwETo5f-8MQ7oMDE5IUYJi_Y/pubhtml?gid=0&single=true) prior to submitting by completing the [contributor license agreement](http://www.mattermost.org/mattermost-contributor-agreement/).
diff --git a/doc/developer/Setup.md b/doc/developer/Setup.md
index c63bde512..afaef4ee4 100644
--- a/doc/developer/Setup.md
+++ b/doc/developer/Setup.md
@@ -11,7 +11,7 @@ Developer Machine Setup
`docker-machine ip dev`
3. Add a line to your /etc/hosts that goes `<Docker IP> dockerhost`
4. Run `docker-machine env dev` and copy the export statements to your ~/.bash_profile
-2. Download Go (version 1.4.2) from http://golang.org/dl/
+2. Download Go (version 1.4.2 or 1.5.1. Final release bits are built with 1.4.2) from http://golang.org/dl/
3. Set up your Go workspace
1. `mkdir ~/go`
2. Add the following to your ~/.bash_profile
@@ -51,7 +51,7 @@ Any issues? Please let us know on our forums at: http://forum.mattermost.org
`127.0.0.1 dockerhost`
3. Install build essentials
1. `apt-get install build-essential`
-4. Download Go (version 1.4.2) from http://golang.org/dl/
+4. Download Go (version 1.4.2 or 1.5.1. Final release bits are built with 1.4.2) from http://golang.org/dl/
5. Set up your Go workspace and add Go to the PATH
1. `mkdir ~/go`
2. Add the following to your ~/.bashrc
diff --git a/doc/install/Configuration-Settings.md b/doc/install/Configuration-Settings.md
new file mode 100644
index 000000000..804a94c30
--- /dev/null
+++ b/doc/install/Configuration-Settings.md
@@ -0,0 +1,302 @@
+## System Console Settings
+
+The System Console user interface lets system administrators manage a Mattermost server and multiple teams from a web-based user interface. The first user added to a new Mattermost install is assigned the system administrator role and can access the System Console from the main menu of any team. Setting changes in the System Console are stored in `config.json`.
+
+### Service Settings
+
+General settings to configure the listening address, login security, testing, webhooks and service integration of Mattermost.
+
+#### System
+
+```"ListenAddress": ":8065"```
+The IP address on which to listen and the port on which to bind. Entering ":8065" will bind to all interfaces or you can choose one like "127.0.0.1:8065". Changing this will require a server restart before taking effect.
+
+```"MaximumLoginAttempts": 10```
+Failed login attempts allowed before a user is locked out and required to reset their password via email.
+
+```"SegmentDeveloperKey": ""```
+For users running SaaS services, signup for a key at Segment.com to track metrics.
+
+```"GoogleDeveloperKey": ""```
+Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at https://www.youtube.com/watch?v=Im69kzhpR3I. Leaving the field blank disables the automatic generation of YouTube video previews from links.
+
+```"EnableTesting": false```
+"true": `/loadtest` slash command is enabled to load test accounts and test data.
+
+```"EnableSecurityFixAlert": true```
+”true”: System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.
+
+#### Webhooks
+
+```"EnableIncomingWebhooks": true```
+Developers building integrations can create webhook URLs for channels and private groups. Please see http://mattermost.org/webhooks to learn about creating webhooks, view samples, and to let the community know about integrations you have built. "true": Incoming webhooks will be allowed. To manage incoming webhooks, go to Account Settings -> Integrations. The webhook URLs created in Account Settings can be used by external applications to create posts in any channels or private groups that you have access to; “false”: The Integrations tab of Account Settings is hidden and incoming webhooks are disabled.
+
+Security note: By enabling this feature, users may be able to perform [phishing attacks](https://en.wikipedia.org/wiki/Phishing) by attempting to impersonate other users. To combat these attacks, a BOT tag appears next to all posts from a webhook. Enable at your own risk.
+
+```"EnablePostUsernameOverride": false```
+"true": Webhooks will be allowed to change the username they are posting as; “false”: Webhooks can only post as the username they were set up with. See http://mattermost.org/webhooks for more details.
+
+```"EnablePostIconOverride": false```
+"true": Webhooks will be allowed to change the icon they post with; “false”: Webhooks can only post with the profile picture of the account they were set up with. See http://mattermost.org/webhooks for more details.
+
+### Team Settings
+
+Settings to configure the appearance, size, and access options for teams.
+
+```"SiteName": "Mattermost"```
+Name of service shown in login screens and UI.
+
+```"MaxUsersPerTeam": 50```
+Maximum number of users per team, including both active and inactive users.
+
+```"EnableTeamCreation": true```
+"true": Ability to create a new team is enabled for all users; “false”: the ability to create teams is disabled. The Create A New Team button is hidden in the main menu UI.
+
+```"EnableUserCreation": true```
+"true": Ability to create new accounts is enabled via inviting new members or sharing the team invite link; “false”: the ability to create accounts is disabled. The create account button displays an error when trying to signup via an email invite or team invite link.
+
+```"RestrictCreationToDomains": ""```
+Teams can only be created by a verified email from this list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").
+
+
+### SQL Settings
+
+Settings to configure the data sources, connections, and encryption of SQL databases. Changing properties in this section will require a server restart before taking effect.
+
+```"DriverName": "mysql"```
+"mysql": enables driver to MySQL database; "postgres": enables driver to PostgreSQL database. This setting can only be changed from config.json file, it cannot be changed from the System Console user interface.
+
+```"DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8"```
+This is the connection string to the master database. When **DriverName**="postgres" then use a connection string in the form “postgres://mmuser:password@localhost:5432/mattermost_test?sslmode=disable&connect_timeout=10”. This setting can only be changed from config.json file, it cannot be changed from the System Console user interface.
+
+```"DataSourceReplicas": []```
+This is a list of connection strings pointing to read replicas of MySQL or PostgreSQL database. If running a single server, set to DataSource. This setting can only be changed from config.json file, it cannot be changed from the System Console user interface.
+
+```"MaxIdleConns": 10```
+Maximum number of idle connections held open to the database.
+
+```"MaxOpenConns": 10```
+Maximum number of open connections held open to the database.
+
+```"Trace": false```
+"true": Executing SQL statements are written to the log for development.
+
+```"AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QVg"```
+32-character (to be randomly generated via Admin Console) salt available to encrypt and decrypt sensitive fields in database.
+
+
+### Email Settings
+
+Settings to configure email signup, notifications, security, and SMTP options.
+
+#### Signup
+
+```"EnableSignUpWithEmail": true```
+"true": Allow team creation and account signup using email and password; “false”: Email signup is disabled and users are not able to invite new members. This limits signup to single-sign-on services like OAuth or LDAP.
+
+#### Notifications
+
+```"SendEmailNotifications": false```
+"true": Enables sending of email notifications. “false”: Disables email notifications for developers who may want to skip email setup for faster development.
+
+```"RequireEmailVerification": false```
+"true": Require email verification after account creation prior to allowing login; “false”: Users do not need to verify their email address prior to login. Developers may set this field to false so skip sending verification emails for faster development.
+
+```"FeedbackName": ""```
+Name displayed on email account used when sending notification emails from Mattermost system.
+
+```"FeedbackEmail": ""```
+Address displayed on email account used when sending notification emails from Mattermost system.
+
+#### SMTP
+
+```"SMTPUsername": ""```
+Obtain this credential from the administrator setting up your email server.
+
+```"SMTPPassword": ""```
+Obtain this credential from the administrator setting up your email server.
+
+```"SMTPServer": ""```
+Location of SMTP email server.
+
+```"SMTPPort": ""```
+Port of SMTP email server.
+
+#### Security
+
+```"ConnectionSecurity": ""```
+"none": Send email over an unsecure connection; "TLS": Communication between Mattermost and your email server is encrypted; “STARTTLS”: Attempts to upgrade an existing insecure connection to a secure connection using TLS.
+
+```"InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS"```
+32-character (to be randomly generated via Admin Console) salt added to signing of email invites.
+
+
+```"PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL"```
+32-character (to be randomly generated via Admin Console) salt added to signing of password reset emails.
+
+
+### File Settings
+
+Settings to configure storage, appearance, and security of files and images.
+
+#### File Storage
+
+```"DriverName": "local"```
+System used for file storage. “local”: Files and images are stored on the local file system. “amazons3”: Files and images are stored on Amazon S3 based on the provided access key, bucket and region fields.
+
+```"Directory": "./data/"```
+Directory to which files are written. If blank, directory will be set to ./data/.
+
+```"AmazonS3AccessKeyId": ""```
+Obtain this credential from your Amazon EC2 administrator.
+
+```"AmazonS3SecretAccessKey": ""```
+Obtain this credential from your Amazon EC2 administrator.
+
+```"AmazonS3Bucket": ""```
+Name you selected for your S3 bucket in AWS.
+
+```"AmazonS3Region": ""```
+AWS region you selected for creating your S3 bucket. Refer to [AWS Reference Documentation](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region) and choose this variable from the Region column.
+
+#### Image Settings
+
+```"ThumbnailWidth": 120```
+Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.
+
+```"ThumbnailHeight": 100```
+Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.
+
+```"PreviewWidth": 1024```
+Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.
+
+```"PreviewHeight": 0```
+Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.
+
+```"ProfileWidth": 128```
+The width to which profile pictures are resized after being uploaded via Account Settings.
+
+```"ProfileHeight": 128```
+The height to which profile pictures are resized after being uploaded via Account Settings.
+
+```"EnablePublicLink": true```
+”true”: Allow users to share public links to files and images when previewing; “false”: The Get Public Link option is hidden from the image preview user interface.
+
+```"PublicLinkSalt": "A705AklYF8MFDOfcwh3I488G8vtLlVip"```
+32-character (to be randomly generated via Admin Console) salt added to signing of public image links.
+
+
+### Log Settings
+
+Settings to configure the console and log file output, detail level, format and location of error messages.
+
+#### Console Settings
+
+```"EnableConsole": true```
+“true”: Output log messages to the console based on **ConsoleLevel** option. The server writes messages to the standard output stream (stdout).
+
+```"ConsoleLevel": "DEBUG"```
+Level of detail at which log events are written to the console when **EnableConsole**=true. ”ERROR”: Outputs only error messages; “INFO”: Outputs error messages and information around startup and initialization; “DEBUG”: Prints high detail for developers debugging issues.
+
+#### Log File Settings
+
+```"EnableFile": true```
+”true”: Log files are written to files specified in **FileLocation**.
+
+```"FileLevel": "INFO"```
+Level of detail at which log events are written to log files when **EnableFile**=true. “ERROR”: Outputs only error messages; “INFO”: Outputs error messages and information around startup and initialization; “DEBUG”: Prints high detail for developers debugging issues.
+
+```"FileFormat": ""```
+Format of log message output. If blank, **FileFormat** = "[%D %T] [%L] (%S) %M", where:
+
+ %T Time (15:04:05 MST)
+ %t Time (15:04)
+ %D Date (2006/01/02)
+ %d Date (01/02/06)
+ %L Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
+ %S Source
+ %M Message
+
+```"FileLocation": ""```
+Directory to which log files are written. If blank, log files write to ./logs/mattermost/mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.
+
+### Rate Limit Settings
+
+Settings to enable API rate limiting and configure requests per second, user sessions and variables for rate limiting. Changing properties in this section will require a server restart before taking effect.
+
+```"EnableRateLimiter": true```
+”true”: APIs are throttled at the rate specified by **PerSec**.
+
+```"PerSec": 10```
+Throttle API at this number of requests per second if **EnableRateLimiter**=true.
+
+```"MemoryStoreSize": 10000```
+Maximum number of user sessions connected to the system as determined by **VaryByRemoteAddr** and **VaryByHeader** variables.
+
+```"VaryByRemoteAddr": true```
+"true": Rate limit API access by IP address.
+
+```"VaryByHeader": ""```
+Vary rate limiting by HTTP header field specified (e.g. when configuring Ngnix set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").
+
+### Privacy Settings
+
+Settings to configure the name and email privacy of users on your system.
+
+```"ShowEmailAddress": true```
+“true”: Show email address of all users; "false": Hide email address of users from other users in the user interface, including team owners and team administrators. This is designed for managing teams where users choose to keep their contact information private.
+
+```"ShowFullName": true```
+”true”: Show full name of all users; “false”: hide full name of users from other users including team owner and team administrators.
+
+### GitLab Settings
+
+Settings to configure account and team creation using GitLab OAuth.
+
+```"Enable": false```
+“true”: Allow team creation and account signup using GitLab OAuth. To configure, input the **Secret** and **Id** credentials.
+
+```"Secret": ""```
+Obtain this value by logging into your GitLab account. Go to Profile Settings -> Applications -> New Application, enter a Name, then enter Redirect URLs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete".
+
+```"Id": ""```
+Obtain this value by logging into your GitLab account. Go to Applications -> Profile Settings. Enter Redirect URLs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete".
+
+```"AuthEndpoint": ""```
+Enter <your-gitlab-url>/oauth/authorize (example: http://localhost:3000/oauth/authorize). Use HTTP or HTTPS in your URLs as appropriate.
+
+```"TokenEndpoint": ""```
+Enter <your-gitlab-url>/oauth/token (example: http://localhost:3000/oauth/token). Use HTTP or HTTPS in your URLs as appropriate.
+
+```"UserApiEndpoint": ""```
+Enter <your-gitlab-url>/api/v3/user (example: http://localhost:3000/api/v3/user). Use HTTP or HTTPS in your URLs as appropriate.
+
+## Config.json Settings Not in System Console
+
+System Console allows an IT Admin to update settings defined in `config.json`. However there are a number of settings in `config.json` unavailable in the System Console and require update from the file itself. We describe them here:
+
+### Service Settings
+
+```"EnableOAuthServiceProvider": false```
+”true”: Allow Mattermost to function as an OAuth provider, allowing 3rd party apps access to your user store for authentication.
+
+### Push Notification Settings
+
+```"ApplePushServer": ""```
+Setting for features in development.
+
+```"ApplePushCertPublic": ""```
+Setting for features in development.
+
+```"ApplePushCertPrivate": ""```
+Setting for features in development.
+
+### File Settings
+
+```"InitialFont": "luximbi.ttf"```
+Font used in auto-generated profile pics with colored backgrounds.
+
+### GitLab Settings
+
+```"Scope": ""```
+Standard setting for OAuth to determine the scope of information shared with OAuth client. Not currently supported by GitLab OAuth.
diff --git a/doc/install/SMTP-Email-Setup.md b/doc/install/SMTP-Email-Setup.md
index 86e2bb20e..8700ac637 100644
--- a/doc/install/SMTP-Email-Setup.md
+++ b/doc/install/SMTP-Email-Setup.md
@@ -39,3 +39,17 @@ To enable email, turn this option off by setting `ByPassEmail=false` and configu
2. Kill the process `kill pid`
3. The service should restart automatically. Verify the Mattermost service is running with `ps -A`
4. Current logged in users will not be affected, but upon logging out or session expiration users will be required to verify their email address.
+
+### Troubleshooting SMTP
+
+If you receive an error message during SMTP setup, do a web search for the error code number to narrow down the issue. Sometimes ISPs require nuanced setups for SMTP and error codes can hint at how to make the proper adjustments.
+
+For example, if you have an error code reading:
+
+```
+Connection unsuccessful: Failed to add to email address - 554 5.7.1 <unknown[IP-ADDRESS]>: Client host rejected: Access denied
+```
+
+Search for `554 5.7.1 error` and `Client host rejected: Access denied`.
+
+
diff --git a/docker/1.1/config_docker.json b/docker/1.1/config_docker.json
index ab5b0a7be..653b6ffd7 100644
--- a/docker/1.1/config_docker.json
+++ b/docker/1.1/config_docker.json
@@ -8,7 +8,8 @@
"EnableIncomingWebhooks": true,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
- "EnableTesting": false
+ "EnableTesting": false,
+ "EnableSecurityFixAlert": true
},
"TeamSettings": {
"SiteName": "Mattermost",
@@ -77,8 +78,7 @@
},
"PrivacySettings": {
"ShowEmailAddress": true,
- "ShowFullName": true,
- "EnableSecurityFixAlert": true
+ "ShowFullName": true
},
"GitLabSettings": {
"Enable": false,
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index ab5b0a7be..653b6ffd7 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -8,7 +8,8 @@
"EnableIncomingWebhooks": true,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
- "EnableTesting": false
+ "EnableTesting": false,
+ "EnableSecurityFixAlert": true
},
"TeamSettings": {
"SiteName": "Mattermost",
@@ -77,8 +78,7 @@
},
"PrivacySettings": {
"ShowEmailAddress": true,
- "ShowFullName": true,
- "EnableSecurityFixAlert": true
+ "ShowFullName": true
},
"GitLabSettings": {
"Enable": false,
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index ab5b0a7be..653b6ffd7 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -8,7 +8,8 @@
"EnableIncomingWebhooks": true,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
- "EnableTesting": false
+ "EnableTesting": false,
+ "EnableSecurityFixAlert": true
},
"TeamSettings": {
"SiteName": "Mattermost",
@@ -77,8 +78,7 @@
},
"PrivacySettings": {
"ShowEmailAddress": true,
- "ShowFullName": true,
- "EnableSecurityFixAlert": true
+ "ShowFullName": true
},
"GitLabSettings": {
"Enable": false,
diff --git a/mattermost.go b/mattermost.go
index 6c0f0a1bf..d2a0567f4 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -81,28 +81,28 @@ func main() {
func securityAndDiagnosticsJob() {
go func() {
for {
- if utils.Cfg.PrivacySettings.EnableSecurityFixAlert && model.IsOfficalBuild() {
+ if *utils.Cfg.ServiceSettings.EnableSecurityFixAlert {
if result := <-api.Srv.Store.System().Get(); result.Err == nil {
props := result.Data.(model.StringMap)
lastSecurityTime, _ := strconv.ParseInt(props["LastSecurityTime"], 10, 0)
currentTime := model.GetMillis()
- id := props["DiagnosticId"]
- if len(id) == 0 {
- id = model.NewId()
- systemId := &model.System{Name: "DiagnosticId", Value: id}
- <-api.Srv.Store.System().Save(systemId)
- }
+ if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
+ l4g.Debug("Checking for security update from Mattermost")
- v := url.Values{}
- v.Set(utils.PROP_DIAGNOSTIC_ID, id)
- v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
- v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
- v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
- v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
+ id := props["DiagnosticId"]
+ if len(id) == 0 {
+ id = model.NewId()
+ systemId := &model.System{Name: "DiagnosticId", Value: id}
+ <-api.Srv.Store.System().Save(systemId)
+ }
- if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
- l4g.Info("Checking for security update from Mattermost")
+ v := url.Values{}
+ v.Set(utils.PROP_DIAGNOSTIC_ID, id)
+ v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
+ v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
+ v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
+ v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
systemSecurityLastTime := &model.System{Name: "LastSecurityTime", Value: strconv.FormatInt(currentTime, 10)}
if lastSecurityTime == 0 {
@@ -111,6 +111,14 @@ func securityAndDiagnosticsJob() {
<-api.Srv.Store.System().Update(systemSecurityLastTime)
}
+ if ucr := <-api.Srv.Store.User().GetTotalUsersCount(); ucr.Err == nil {
+ v.Set(utils.PROP_DIAGNOSTIC_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
+ }
+
+ if ucr := <-api.Srv.Store.User().GetTotalActiveUsersCount(); ucr.Err == nil {
+ v.Set(utils.PROP_DIAGNOSTIC_ACTIVE_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
+ }
+
res, err := http.Get(utils.DIAGNOSTIC_URL + "/security?" + v.Encode())
if err != nil {
l4g.Error("Failed to get security update information from Mattermost.")
diff --git a/model/config.go b/model/config.go
index e4b99ad4a..8a11b7bb7 100644
--- a/model/config.go
+++ b/model/config.go
@@ -32,6 +32,7 @@ type ServiceSettings struct {
EnablePostUsernameOverride bool
EnablePostIconOverride bool
EnableTesting bool
+ EnableSecurityFixAlert *bool
}
type SSOSettings struct {
@@ -110,9 +111,8 @@ type RateLimitSettings struct {
}
type PrivacySettings struct {
- ShowEmailAddress bool
- ShowFullName bool
- EnableSecurityFixAlert bool
+ ShowEmailAddress bool
+ ShowFullName bool
}
type TeamSettings struct {
@@ -163,6 +163,13 @@ func ConfigFromJson(data io.Reader) *Config {
}
}
+func (o *Config) SetDefaults() {
+ if o.ServiceSettings.EnableSecurityFixAlert == nil {
+ o.ServiceSettings.EnableSecurityFixAlert = new(bool)
+ *o.ServiceSettings.EnableSecurityFixAlert = true
+ }
+}
+
func (o *Config) IsValid() *AppError {
if o.ServiceSettings.MaximumLoginAttempts <= 0 {
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 011acd7e4..dc6b07a16 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -530,3 +530,24 @@ func (us SqlUserStore) GetTotalUsersCount() StoreChannel {
return storeChannel
}
+
+func (us SqlUserStore) GetTotalActiveUsersCount() StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ time := model.GetMillis() - (1000 * 60 * 60 * 12)
+
+ if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time}); err != nil {
+ result.Err = model.NewAppError("SqlUserStore.GetTotalActiveUsersCount", "We could not count the users", err.Error())
+ } else {
+ result.Data = count
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
diff --git a/store/sql_user_store_test.go b/store/sql_user_store_test.go
index be21c8bd2..874baf634 100644
--- a/store/sql_user_store_test.go
+++ b/store/sql_user_store_test.go
@@ -206,7 +206,7 @@ func TestUserStoreGet(t *testing.T) {
}
}
-func TestUserCountt(t *testing.T) {
+func TestUserCount(t *testing.T) {
Setup()
u1 := model.User{}
@@ -224,6 +224,24 @@ func TestUserCountt(t *testing.T) {
}
}
+func TestActiveUserCount(t *testing.T) {
+ Setup()
+
+ u1 := model.User{}
+ u1.TeamId = model.NewId()
+ u1.Email = model.NewId()
+ Must(store.User().Save(&u1))
+
+ if result := <-store.User().GetTotalActiveUsersCount(); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ count := result.Data.(int64)
+ if count <= 0 {
+ t.Fatal()
+ }
+ }
+}
+
func TestUserStoreGetProfiles(t *testing.T) {
Setup()
diff --git a/store/store.go b/store/store.go
index 1c4d08e36..e539bc98a 100644
--- a/store/store.go
+++ b/store/store.go
@@ -104,6 +104,7 @@ type UserStore interface {
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
GetForExport(teamId string) StoreChannel
GetTotalUsersCount() StoreChannel
+ GetTotalActiveUsersCount() StoreChannel
GetSystemAdminProfiles() StoreChannel
}
diff --git a/utils/config.go b/utils/config.go
index 44ee14a6e..2c6f30bf0 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -150,6 +150,8 @@ func LoadConfig(fileName string) {
CfgFileName = fileName
}
+ config.SetDefaults()
+
if err := config.IsValid(); err != nil {
panic("Error validating config file=" + fileName + ", err=" + err.Message)
}
diff --git a/utils/diagnostic.go b/utils/diagnostic.go
index da02e771b..8572c2f51 100644
--- a/utils/diagnostic.go
+++ b/utils/diagnostic.go
@@ -6,24 +6,23 @@ package utils
import (
"net/http"
"net/url"
-
- "github.com/mattermost/platform/model"
)
const (
DIAGNOSTIC_URL = "https://d7zmvsa9e04kk.cloudfront.net"
- PROP_DIAGNOSTIC_ID = "id"
- PROP_DIAGNOSTIC_CATEGORY = "c"
- VAL_DIAGNOSTIC_CATEGORY_DEFAULT = "d"
- PROP_DIAGNOSTIC_BUILD = "b"
- PROP_DIAGNOSTIC_DATABASE = "db"
- PROP_DIAGNOSTIC_OS = "os"
- PROP_DIAGNOSTIC_USER_COUNT = "uc"
+ PROP_DIAGNOSTIC_ID = "id"
+ PROP_DIAGNOSTIC_CATEGORY = "c"
+ VAL_DIAGNOSTIC_CATEGORY_DEFAULT = "d"
+ PROP_DIAGNOSTIC_BUILD = "b"
+ PROP_DIAGNOSTIC_DATABASE = "db"
+ PROP_DIAGNOSTIC_OS = "os"
+ PROP_DIAGNOSTIC_USER_COUNT = "uc"
+ PROP_DIAGNOSTIC_ACTIVE_USER_COUNT = "auc"
)
func SendDiagnostic(values url.Values) {
- if Cfg.PrivacySettings.EnableSecurityFixAlert && model.IsOfficalBuild() {
+ if *Cfg.ServiceSettings.EnableSecurityFixAlert {
res, err := http.Get(DIAGNOSTIC_URL + "/i?" + values.Encode())
if err != nil {
diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx
index a32ca3136..70ec04f4a 100644
--- a/web/react/components/admin_console/privacy_settings.jsx
+++ b/web/react/components/admin_console/privacy_settings.jsx
@@ -30,7 +30,6 @@ export default class PrivacySettings extends React.Component {
var config = this.props.config;
config.PrivacySettings.ShowEmailAddress = React.findDOMNode(this.refs.ShowEmailAddress).checked;
config.PrivacySettings.ShowFullName = React.findDOMNode(this.refs.ShowFullName).checked;
- config.PrivacySettings.EnableSecurityFixAlert = React.findDOMNode(this.refs.EnableSecurityFixAlert).checked;
Client.saveConfig(
config,
@@ -138,39 +137,6 @@ export default class PrivacySettings extends React.Component {
</div>
<div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='EnableSecurityFixAlert'
- >
- {'Send Error and Diagnostic: '}
- </label>
- <div className='col-sm-8'>
- <label className='radio-inline'>
- <input
- type='radio'
- name='EnableSecurityFixAlert'
- value='true'
- ref='EnableSecurityFixAlert'
- defaultChecked={this.props.config.PrivacySettings.EnableSecurityFixAlert}
- onChange={this.handleChange}
- />
- {'true'}
- </label>
- <label className='radio-inline'>
- <input
- type='radio'
- name='EnableSecurityFixAlert'
- value='false'
- defaultChecked={!this.props.config.PrivacySettings.EnableSecurityFixAlert}
- onChange={this.handleChange}
- />
- {'false'}
- </label>
- <p className='help-text'>{'When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'}</p>
- </div>
- </div>
-
- <div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index 3968d9820..f29d62646 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -35,11 +35,13 @@ export default class ServiceSettings extends React.Component {
config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
- //config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked;
config.ServiceSettings.EnablePostUsernameOverride = React.findDOMNode(this.refs.EnablePostUsernameOverride).checked;
config.ServiceSettings.EnablePostIconOverride = React.findDOMNode(this.refs.EnablePostIconOverride).checked;
config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked;
+ config.ServiceSettings.EnableSecurityFixAlert = React.findDOMNode(this.refs.EnableSecurityFixAlert).checked;
+
+ //config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
var MaximumLoginAttempts = 10;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) {
@@ -305,6 +307,39 @@ export default class ServiceSettings extends React.Component {
</div>
<div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='EnableSecurityFixAlert'
+ >
+ {'Enable Security Alerts: '}
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableSecurityFixAlert'
+ value='true'
+ ref='EnableSecurityFixAlert'
+ defaultChecked={this.props.config.ServiceSettings.EnableSecurityFixAlert}
+ onChange={this.handleChange}
+ />
+ {'true'}
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableSecurityFixAlert'
+ value='false'
+ defaultChecked={!this.props.config.ServiceSettings.EnableSecurityFixAlert}
+ onChange={this.handleChange}
+ />
+ {'false'}
+ </label>
+ <p className='help-text'>{'When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index d16069725..d0d6ab5e2 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -104,12 +104,6 @@ export default class ChannelLoader extends React.Component {
}
});
- /* Setup modal events */
- $('.modal').on('show.bs.modal', function onShow() {
- $('.modal-body').css('overflow-y', 'auto');
- $('.modal-body').css('max-height', $(window).height() * 0.7);
- });
-
/* Prevent backspace from navigating back a page */
$(window).on('keydown.preventBackspace', (e) => {
if (e.which === 8 && !$(e.target).is('input, textarea')) {
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index add4125d7..680d693f1 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -262,25 +262,27 @@ export default class CreateComment extends React.Component {
id={this.props.rootId}
className='post-create-body comment-create-body'
>
- <Textbox
- onUserInput={this.handleUserInput}
- onKeyPress={this.commentMsgKeyPress}
- messageText={this.state.messageText}
- createMessage='Add a comment...'
- initialText=''
- id='reply_textbox'
- ref='textbox'
- />
- <FileUpload
- ref='fileUpload'
- getFileCount={this.getFileCount}
- onUploadStart={this.handleUploadStart}
- onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError}
- onTextDrop={this.handleTextDrop}
- postType='comment'
- channelId={this.props.channelId}
- />
+ <div className='post-body__cell'>
+ <Textbox
+ onUserInput={this.handleUserInput}
+ onKeyPress={this.commentMsgKeyPress}
+ messageText={this.state.messageText}
+ createMessage='Add a comment...'
+ initialText=''
+ id='reply_textbox'
+ ref='textbox'
+ />
+ <FileUpload
+ ref='fileUpload'
+ getFileCount={this.getFileCount}
+ onUploadStart={this.handleUploadStart}
+ onFileUpload={this.handleFileUploadComplete}
+ onUploadError={this.handleUploadError}
+ onTextDrop={this.handleTextDrop}
+ postType='comment'
+ channelId={this.props.channelId}
+ />
+ </div>
</div>
<MsgTyping
channelId={this.props.channelId}
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index 234013b93..eb6bfa9b6 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -96,7 +96,6 @@ export default class GetLinkModal extends React.Component {
<p>
Send teammates the link below for them to sign-up to this team site.
<br /><br />
- Be careful not to share this link publicly, since anyone with the link can join your team.
</p>
<textarea
className='form-control no-resize'
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index d2cbc798e..4f0fe3ed0 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -32,11 +32,17 @@ export default class SettingItemMax extends React.Component {
}
var inputs = this.props.inputs;
+ var widthClass;
+ if (this.props.width === 'full') {
+ widthClass = 'col-sm-12';
+ } else {
+ widthClass = 'col-sm-9 col-sm-offset-3';
+ }
return (
<ul className='section-max form-horizontal'>
<li className='col-sm-12 section-title'>{this.props.title}</li>
- <li className='col-sm-9 col-sm-offset-3'>
+ <li className={widthClass}>
<ul className='setting-list'>
<li className='setting-list-item'>
{inputs}
@@ -69,5 +75,6 @@ SettingItemMax.propTypes = {
extraInfo: React.PropTypes.element,
updateSection: React.PropTypes.func,
submit: React.PropTypes.func,
- title: React.PropTypes.string
+ title: React.PropTypes.string,
+ width: React.PropTypes.string
};
diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx
index b5d2132d7..66568e1c8 100644
--- a/web/react/components/settings_sidebar.jsx
+++ b/web/react/components/settings_sidebar.jsx
@@ -10,7 +10,7 @@ export default class SettingsSidebar extends React.Component {
handleClick(tab, e) {
e.preventDefault();
this.props.updateTab(tab.name);
- $('.settings-modal').addClass('display--content');
+ $(e.target).closest('.settings-modal').addClass('display--content');
}
render() {
let tabList = this.props.tabs.map(function makeTab(tab) {
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 75661f812..8a3af707f 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -149,7 +149,7 @@ export default class SignupUserComplete extends React.Component {
// set up the email entry and hide it if an email was provided
var yourEmailIs = '';
if (this.state.user.email) {
- yourEmailIs = <span>Your email address is {this.state.user.email}. You'll use this address to sign in to {global.window.config.SiteName}.</span>;
+ yourEmailIs = <span>Your email address is <strong>{this.state.user.email}</strong>. You'll use this address to sign in to {global.window.config.SiteName}.</span>;
}
var emailContainerStyle = 'margin--extra';
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 0563c294a..741dbcd5d 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -246,9 +246,11 @@ export default class Textbox extends React.Component {
if (e.scrollHeight - mod < 167) {
$(e).css({height: 'auto', 'overflow-y': 'hidden'}).height(e.scrollHeight - mod);
$(w).css({height: 'auto'}).height(e.scrollHeight + 2);
+ $(w).closest('.post-body__cell').removeClass('scroll');
} else {
$(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167);
$(w).css({height: 'auto'}).height(167);
+ $(w).closest('.post-body__cell').addClass('scroll');
}
if (prevHeight !== $(e).height() && this.props.onHeightChange) {
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index c23c61948..66d83725c 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -368,8 +368,7 @@ export default class UserSettingsGeneralTab extends React.Component {
const extraInfo = (
<span>
- {'Use Nickname for a name you might be called that is different from your first name and user name.'}
- {'This is most often used when two or more people have similar sounding names and usernames.'}
+ {'Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'}
</span>
);
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index 5e20d41f1..3be062ad3 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -38,6 +38,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
incomingHooksSection = (
<SettingItemMax
title='Incoming Webhooks'
+ width = 'full'
inputs={inputs}
updateSection={function clearSection(e) {
this.updateSection('');
@@ -49,6 +50,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
incomingHooksSection = (
<SettingItemMin
title='Incoming Webhooks'
+ width = 'full'
describe='Manage your incoming webhooks (Developer feature)'
updateSection={function updateNameSection() {
this.updateSection('incoming-hooks');
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 8ef68dd0a..c5f0abc12 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -195,6 +195,7 @@ export default class ViewImageModal extends React.Component {
target='_blank'
>
<img
+ style={{maxHeight: this.state.imgHeight}}
ref='image'
src={this.getPreviewImagePath(filename)}
/>
@@ -210,6 +211,7 @@ export default class ViewImageModal extends React.Component {
content = (
<video
+ style={{maxHeight: this.state.imgHeight}}
ref='video'
data-setup='{}'
controls='controls'
@@ -334,7 +336,6 @@ export default class ViewImageModal extends React.Component {
>
<div
className={'image-wrapper ' + bgClass}
- style={{maxHeight: this.state.imgHeight}}
onMouseEnter={this.onMouseEnterImage}
onMouseLeave={this.onMouseLeaveImage}
onClick={(e) => e.stopPropagation()}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index affc49196..e3cbfccde 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -201,7 +201,7 @@ module.exports = {
centerChannelBg: '#1F1F1F',
centerChannelColor: '#DDDDDD',
newMessageSeparator: '#CC992D',
- linkColor: '#0177e7',
+ linkColor: '#0D93FF',
buttonBg: '#0177e7',
buttonColor: '#FFFFFF',
mentionHighlightBg: '#784098',
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index f9166063e..6a5188974 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -423,7 +423,10 @@ export function applyTheme(theme) {
if (theme.sidebarTextActiveColor) {
changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor, 2);
- changeCss('.sidebar--left .nav-pills__container li.active a .status .online--icon', 'fill:' + theme.sidebarTextActiveColor, 2);
+ }
+
+ if (theme.sidebarTextActiveBg === theme.onlineIndicator) {
+ changeCss('.sidebar--left .nav-pills__container li.active a .status .online--icon', 'fill:' + theme.sidebarTextActiveColor, 1);
}
if (theme.sidebarHeaderBg) {
@@ -497,7 +500,7 @@ export function applyTheme(theme) {
changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro, .settings-modal .settings-table .settings-content .divider-dark, hr, .settings-modal .settings-table .settings-links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, pre', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body, .modal .more-channel-table tbody>tr td, .member-div:first-child, .member-div, .access-history__table .access__report, .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2);
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 18462d92a..470db16dc 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -34,6 +34,11 @@ body {
}
}
+img {
+ max-width: 100%;
+ height: auto;
+}
+
.input-group-addon {
background: transparent;
}
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 8e353aff9..feaa5acfb 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -147,7 +147,8 @@
}
.header__info {
color: #fff;
- padding-left: 4px;
+ @include clearfix;
+ padding-left: 2px;
z-index: 1;
position: relative;
}
@@ -155,7 +156,7 @@
display: block;
font-weight: 600;
font-size: 16px;
- max-width: 80%;
+ max-width: 85%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
diff --git a/web/sass-files/sass/partials/_markdown.scss b/web/sass-files/sass/partials/_markdown.scss
index 122586354..1aa942ad0 100644
--- a/web/sass-files/sass/partials/_markdown.scss
+++ b/web/sass-files/sass/partials/_markdown.scss
@@ -53,15 +53,10 @@ blockquote {
}
pre {
border: none;
- background-color: #f7f7f7;
margin: 5px 0;
- .current--user & {
- background: #fff;
- }
- code {
- color: #c7254e;
- }
+ color: inherit;
}
code {
background: #fff;
+ color: inherit;
}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 2722333a4..90ea8ce2c 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -1,3 +1,6 @@
+#channel_members_modal .modal-body {
+ min-height: 110px;
+}
.modal-body {
padding: 20px 15px;
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index ccd7fd425..85bb2eb28 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -195,6 +195,14 @@ body.ios {
.post-body__cell {
vertical-align: top;
position: relative;
+ &.scroll {
+ .btn-file {
+ right: 15px;
+ }
+ .custom-textarea {
+ padding-right: 43px;
+ }
+ }
}
.send-button {
display: none;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 82ec1811a..4e532c16e 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -421,6 +421,9 @@
.post-body__cell {
display: table-cell;
padding-left: 45px;
+ .sidebar--right & {
+ padding-left: 0;
+ }
}
.app__content & {
.btn-file {
@@ -667,7 +670,7 @@
.modal-image {
.image-wrapper {
font-size: 12px;
- max-width: 280px;
+ min-width: 280px;
.modal-close {
@include opacity(1);
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 8debb0b4e..0c2f25eab 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -268,7 +268,6 @@
position:absolute;
right:15px;
top:13px;
- color:#414142;
}
}
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index b37dbf421..c954b03d8 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -26,6 +26,7 @@
.post-header {
.post-header-col {
&.post-header__reply {
+ min-width: 30px;
text-align: right;
float: right;
}
diff --git a/web/templates/channel.html b/web/templates/channel.html
index 2af94e415..13fd80d75 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -54,7 +54,7 @@
<script>
window.setup_channel_page({{ .Props }});
$('body').tooltip( {selector: '[data-toggle=tooltip]'} );
- $('.modal-body').css('max-height', $(window).height() * 0.7);
+ $('.modal-body').css('max-height', $(window).height() - 150);
$('.modal-body').perfectScrollbar();
</script>
</body>