summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md17
-rw-r--r--Makefile4
-rw-r--r--NOTICE.txt123
-rw-r--r--README.md4
-rw-r--r--api/admin.go35
-rw-r--r--api/command_test.go3
-rw-r--r--api/context.go27
-rw-r--r--api/file.go2
-rw-r--r--api/post.go4
-rw-r--r--api/team.go13
-rw-r--r--api/user.go11
-rw-r--r--api/webhook.go2
-rw-r--r--build/MIT-COMPILED-LICENSE.md14
-rw-r--r--config/config.json2
-rw-r--r--doc/install/prod-ubuntu.md187
-rw-r--r--docker/0.5/Dockerfile100
-rwxr-xr-xdocker/0.5/Dockerrun.aws.json13
-rwxr-xr-xdocker/0.5/docker-entry.sh122
-rw-r--r--docker/0.5/main.cf28
-rw-r--r--docker/0.6/Dockerrun.aws.zipbin867 -> 0 bytes
-rw-r--r--docker/0.6/config_docker.json99
-rw-r--r--docker/1.0/Dockerfile (renamed from docker/0.6/Dockerfile)8
-rw-r--r--docker/1.0/Dockerrun.aws.zipbin0 -> 711 bytes
-rw-r--r--docker/1.0/Dockerrun.aws/.ebextensions/01_files.config (renamed from docker/0.6/Dockerrun.aws/.ebextensions/01_files.config)0
-rwxr-xr-xdocker/1.0/Dockerrun.aws/Dockerrun.aws.json (renamed from docker/0.6/Dockerrun.aws/Dockerrun.aws.json)2
-rw-r--r--docker/1.0/config_docker.json89
-rwxr-xr-xdocker/1.0/docker-entry.sh (renamed from docker/0.6/docker-entry.sh)0
-rw-r--r--docker/dev/config_docker.json8
-rw-r--r--docker/local/config_docker.json8
-rw-r--r--model/config.go92
-rw-r--r--model/user.go4
-rw-r--r--model/version.go2
-rw-r--r--model/version_test.go2
-rw-r--r--store/sql_channel_store.go4
-rw-r--r--store/sql_post_store.go4
-rw-r--r--store/sql_post_store_test.go7
-rw-r--r--store/sql_store.go22
-rw-r--r--utils/config.go5
-rw-r--r--utils/mail.go23
-rw-r--r--web/react/components/admin_console/email_settings.jsx4
-rw-r--r--web/react/components/admin_console/gitlab_settings.jsx105
-rw-r--r--web/react/components/navbar_dropdown.jsx2
-rw-r--r--web/react/components/setting_upload.jsx45
-rw-r--r--web/react/components/sidebar_right.jsx4
-rw-r--r--web/react/components/sidebar_right_menu.jsx15
-rw-r--r--web/react/components/team_signup_url_page.jsx4
-rw-r--r--web/react/components/team_signup_with_sso.jsx10
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx2
-rw-r--r--web/react/utils/client.jsx14
-rw-r--r--web/react/utils/utils.jsx17
-rw-r--r--web/sass-files/sass/partials/_responsive.scss8
-rw-r--r--web/sass-files/sass/partials/_search.scss3
-rw-r--r--web/templates/head.html19
53 files changed, 794 insertions, 548 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 64cc57ab8..c4f1f491e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,18 +1,17 @@
# Mattermost Changelog
-## UNDER DEVELOPMENT - Release v1.0.0-RC1
+## UNDER DEVELOPMENT - Release v1.0.0-RC2
The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release.
-- **Release candidate anticipated:** September 28, 2015
- **Final release anticipated:** October 2, 2015
### Release Highlights
- System Console - UI for configuring deployments, managing teams, resetting user passwords and other admin features
-- Markdown support in messages, comments and channel descriptions - Including font formatting, emoticons, headings and tables
-- Preset themes and detailed theme color options, plus ability to import themes from Slack
-- Numerous performance improvements and optimizations
+- Markdown - Markdown support in messages, comments and channel descriptions - Including font formatting, emoticons, headings and tables
+- Themes - Preset themes and detailed theme color options, plus ability to import themes from Slack
+- Performance - Numerous performance improvements and optimizations
### New Features
@@ -39,12 +38,14 @@ User Interface
- Added ability to import themes from Slack
Integrations
+
- (Preview) Initial support for incoming webhooks
### Improvements
Documentation
+- Added production installation instructions
- Updated software and hardware requirements documentation
- Re-organized install instructions out of README and into separate files
- Added Code Contribution Guidelines
@@ -59,11 +60,15 @@ Performance
Code Quality
- Reformatted Javascript per Mattermost Style Guide
-
+
UI
- Added version, build number, build date and build hash under Account Settings -> Security
+Licensing
+
+- Compiled version of Mattermost v1.0.0 now available under MIT license
+
### Bug Fixes
- Numerous performance improvements
diff --git a/Makefile b/Makefile
index 4459da9dd..5cb5467f6 100644
--- a/Makefile
+++ b/Makefile
@@ -92,7 +92,7 @@ travis:
mkdir -p $(DIST_PATH)/api
cp -RL api/templates $(DIST_PATH)/api
- cp LICENSE.txt $(DIST_PATH)
+ cp build/MIT-COMPILED-LICENSE.md $(DIST_PATH)
cp NOTICE.txt $(DIST_PATH)
cp README.md $(DIST_PATH)
@@ -261,7 +261,7 @@ dist: install
mkdir -p $(DIST_PATH)/api
cp -RL api/templates $(DIST_PATH)/api
- cp LICENSE.txt $(DIST_PATH)
+ cp build/MIT-COMPILED-LICENSE.md $(DIST_PATH)
cp NOTICE.txt $(DIST_PATH)
cp README.md $(DIST_PATH)
diff --git a/NOTICE.txt b/NOTICE.txt
index b7a7fbc1d..f908bbd28 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -798,3 +798,126 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+
+---
+
+This product contains a modified portion of 'react-bootstrap', a library of reuseable front-end components with the look and feel of Bootstrap, but rebuilt with React.js.
+
+by Stephen J. Collings, Matthew Honnibal, Pieter Vanderwerff
+
+* HOMEPAGE:
+ * https://github.com/react-bootstrap/react-bootstrap
+
+* LICENSE:
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Stephen J. Collings, Matthew Honnibal, Pieter Vanderwerff
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+---
+
+This product contains a modified portion of 'goexif', which provides decoding of basic exif and tiff encoded data.
+
+by Robert Carlsen & Contributors
+
+* HOMEPAGE:
+ * https://github.com/rwcarlsen/goexif
+
+* LICENSE:
+
+Copyright (c) 2012, Robert Carlsen & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---
+
+This product contains a modified portion of 'graphics-go', an implementation of basic image manipulation operations in the Go programming language.
+
+by The Graphics-Go Authors
+
+* HOMEPAGE:
+ * https://code.google.com/p/graphics-go/
+
+* LICENSE:
+
+Copyright (c) 2011 The Graphics-Go Authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---
+
+This product contains a modified portion of 'Bootstrap Colorpicker', a nice and customizable colorpicker plugin for Twitter Bootstrap.
+
+by Stefan Petre
+
+* HOMEPAGE:
+ * https://github.com/mjolnic/bootstrap-colorpicker/
+
+* LICENSE:
+
+Copyright 2012 Stefan Petre
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
index 58f4887f6..88f6dcb35 100644
--- a/README.md
+++ b/README.md
@@ -52,10 +52,12 @@ There are multiple ways to install Mattermost depending on your needs.
[![Build Status](https://travis-ci.org/mattermost/platform.svg?branch=master)](https://travis-ci.org/mattermost/platform)
-#### Production Deployment (for Beta2 and later)
+#### Production Deployment
Prior to production installation, please review [Mattermost system requirements](doc/install/requirements.md).
+- [Production Install on Ubuntu 14.04](https://github.com/mattermost/platform/blob/release-1.0.0/doc/install/prod-ubuntu.md) - Install Mattermost for production environments.
+
- [GitLab Mattermost Production Installation](https://gitlab.com/gitlab-org/gitlab-mattermost) - Install Mattermost for production environments bundled with GitLab, a leading open source Git repository, using an omnibus package for Ubuntu 12.04, Ubuntu 14.04, Debian 7, Debian 8, and CentOS 6 (and RedHat/Oracle/Scientific Linux 6), CentOS 7 (and RedHat/Oracle/Scientific Linux 7).
For technical questions and answers, please visit the [Mattermost forum](http://forum.mattermost.org).
diff --git a/api/admin.go b/api/admin.go
index 568d8f6e8..d9714d6d2 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -23,8 +23,10 @@ func InitAdmin(r *mux.Router) {
sr.Handle("/logs", ApiUserRequired(getLogs)).Methods("GET")
sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
- sr.Handle("/client_props", ApiAppHandler(getClientProperties)).Methods("GET")
sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
+ sr.Handle("/client_props", ApiAppHandler(getClientProperties)).Methods("GET")
+ sr.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
+
}
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -59,6 +61,26 @@ func getClientProperties(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(utils.ClientProperties)))
}
+func logClient(c *Context, w http.ResponseWriter, r *http.Request) {
+ m := model.MapFromJson(r.Body)
+
+ lvl := m["level"]
+ msg := m["message"]
+
+ if len(msg) > 400 {
+ msg = msg[0:399]
+ }
+
+ if lvl == "ERROR" {
+ err := model.NewAppError("client", msg, "")
+ c.LogError(err)
+ }
+
+ rm := make(map[string]string)
+ rm["SUCCESS"] = "true"
+ w.Write([]byte(model.MapToJson(rm)))
+}
+
func getConfig(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.HasSystemAdminPermissions("getConfig") {
return
@@ -82,18 +104,11 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if len(cfg.ServiceSettings.ListenAddress) == 0 {
- c.SetInvalidParam("saveConfig", "config")
- return
- }
-
- if cfg.TeamSettings.MaxUsersPerTeam == 0 {
- c.SetInvalidParam("saveConfig", "config")
+ if err := cfg.IsValid(); err != nil {
+ c.Err = err
return
}
- // TODO run some cleanup validators
-
utils.SaveConfig(utils.CfgFileName, cfg)
utils.LoadConfig(utils.CfgFileName)
json := utils.Cfg.ToJson()
diff --git a/api/command_test.go b/api/command_test.go
index d70729448..360c4da58 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -6,6 +6,7 @@ package api
import (
"strings"
"testing"
+ "time"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
@@ -176,6 +177,8 @@ func TestEchoCommand(t *testing.T) {
t.Fatal("Echo command failed to execute")
}
+ time.Sleep(100 * time.Millisecond)
+
p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
if len(p1.Order) != 1 {
t.Fatal("Echo command failed to send")
diff --git a/api/context.go b/api/context.go
index d90fbd9ee..02c3dc902 100644
--- a/api/context.go
+++ b/api/context.go
@@ -292,14 +292,6 @@ func (c *Context) HasPermissionsToChannel(sc store.StoreChannel, where string) b
return true
}
-func (c *Context) IsSystemAdmin() bool {
- // TODO XXX FIXME && IsPrivateIpAddress(c.IpAddress)
- if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) {
- return true
- }
- return false
-}
-
func (c *Context) HasSystemAdminPermissions(where string) bool {
if c.IsSystemAdmin() {
return true
@@ -310,14 +302,19 @@ func (c *Context) HasSystemAdminPermissions(where string) bool {
return false
}
-func (c *Context) IsTeamAdmin(userId string) bool {
- if uresult := <-Srv.Store.User().Get(userId); uresult.Err != nil {
- c.Err = uresult.Err
- return false
- } else {
- user := uresult.Data.(*model.User)
- return model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) && user.TeamId == c.Session.TeamId
+func (c *Context) IsSystemAdmin() bool {
+ // TODO XXX FIXME && IsPrivateIpAddress(c.IpAddress)
+ if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) {
+ return true
+ }
+ return false
+}
+
+func (c *Context) IsTeamAdmin() bool {
+ if model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) || c.IsSystemAdmin() {
+ return true
}
+ return false
}
func (c *Context) RemoveSessionCookie(w http.ResponseWriter) {
diff --git a/api/file.go b/api/file.go
index be8fc5456..5dc1db650 100644
--- a/api/file.go
+++ b/api/file.go
@@ -488,7 +488,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
}
func getExport(c *Context, w http.ResponseWriter, r *http.Request) {
- if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin(c.Session.UserId) {
+ if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin() {
c.Err = model.NewAppError("getExport", "Only a team admin can retrieve exported data.", "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/api/post.go b/api/post.go
index 0379f6af5..2b683fb7d 100644
--- a/api/post.go
+++ b/api/post.go
@@ -633,7 +633,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
post := result.Data.(*model.PostList).Posts[postId]
- if !c.HasPermissionsToChannel(cchan, "deletePost") && !c.IsTeamAdmin(post.UserId) {
+ if !c.HasPermissionsToChannel(cchan, "deletePost") && !c.IsTeamAdmin() {
return
}
@@ -648,7 +648,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if post.UserId != c.Session.UserId && !model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) {
+ if post.UserId != c.Session.UserId && !c.IsTeamAdmin() {
c.Err = model.NewAppError("deletePost", "You do not have the appropriate permissions", "")
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/api/team.go b/api/team.go
index 4794b66df..8e5d634aa 100644
--- a/api/team.go
+++ b/api/team.go
@@ -75,7 +75,10 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- m["follow_link"] = bodyPage.Props["Link"]
+ if !utils.Cfg.EmailSettings.RequireEmailVerification {
+ m["follow_link"] = bodyPage.Props["Link"]
+ }
+
w.Header().Set("Access-Control-Allow-Origin", " *")
w.Write([]byte(model.MapToJson(m)))
}
@@ -506,7 +509,7 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
sender := user.GetDisplayName()
senderRole := ""
- if model.IsInRole(user.Roles, model.ROLE_TEAM_ADMIN) || model.IsInRole(user.Roles, model.ROLE_SYSTEM_ADMIN) {
+ if c.IsTeamAdmin() {
senderRole = "administrator"
} else {
senderRole = "member"
@@ -566,7 +569,7 @@ func updateTeamDisplayName(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) {
+ if !c.IsTeamAdmin() {
c.Err = model.NewAppError("updateTeamDisplayName", "You do not have the appropriate permissions", "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
@@ -600,7 +603,7 @@ func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
- if !c.HasPermissionsToTeam(c.Session.TeamId, "import") || !c.IsTeamAdmin(c.Session.UserId) {
+ if !c.HasPermissionsToTeam(c.Session.TeamId, "import") || !c.IsTeamAdmin() {
c.Err = model.NewAppError("importTeam", "Only a team admin can import data.", "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
@@ -667,7 +670,7 @@ func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
func exportTeam(c *Context, w http.ResponseWriter, r *http.Request) {
- if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin(c.Session.UserId) {
+ if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin() {
c.Err = model.NewAppError("exportTeam", "Only a team admin can export data.", "userId="+c.Session.UserId)
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/api/user.go b/api/user.go
index 4240a795e..ed3576a30 100644
--- a/api/user.go
+++ b/api/user.go
@@ -198,7 +198,7 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
l4g.Error("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", ruser.Id, ruser.TeamId, err)
}
- fireAndForgetWelcomeEmail(ruser.Email, team.DisplayName, c.GetTeamURLFromTeam(team))
+ fireAndForgetWelcomeEmail(ruser.Email, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
if user.EmailVerified {
if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil {
l4g.Error("Failed to set email verified err=%v", cresult.Err)
@@ -218,12 +218,13 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
}
}
-func fireAndForgetWelcomeEmail(email, teamDisplayName, teamURL string) {
+func fireAndForgetWelcomeEmail(email, teamDisplayName, siteURL, teamURL string) {
go func() {
subjectPage := NewServerTemplatePage("welcome_subject")
subjectPage.Props["TeamDisplayName"] = teamDisplayName
bodyPage := NewServerTemplatePage("welcome_body")
+ bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["TeamURL"] = teamURL
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
@@ -972,7 +973,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) && !c.IsSystemAdmin() {
+ if !c.IsTeamAdmin() {
c.Err = model.NewAppError("updateRoles", "You do not have the appropriate permissions", "userId="+user_id)
c.Err.StatusCode = http.StatusForbidden
return
@@ -997,7 +998,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) {
} else {
sessions := result.Data.([]*model.Session)
for _, s := range sessions {
- sessionCache.Remove(s.Id)
+ sessionCache.Remove(s.Token)
}
}
@@ -1069,7 +1070,7 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) && !c.IsSystemAdmin() {
+ if !c.IsTeamAdmin() {
c.Err = model.NewAppError("updateActive", "You do not have the appropriate permissions", "userId="+user_id)
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/api/webhook.go b/api/webhook.go
index b67655ff5..e694b202c 100644
--- a/api/webhook.go
+++ b/api/webhook.go
@@ -86,7 +86,7 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = result.Err
return
} else {
- if c.Session.UserId != result.Data.(*model.IncomingWebhook).UserId && !model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) {
+ if c.Session.UserId != result.Data.(*model.IncomingWebhook).UserId && !c.IsTeamAdmin() {
c.LogAudit("fail - inappropriate conditions")
c.Err = model.NewAppError("deleteIncomingHook", "Inappropriate permissions to delete incoming webhook", "user_id="+c.Session.UserId)
return
diff --git a/build/MIT-COMPILED-LICENSE.md b/build/MIT-COMPILED-LICENSE.md
new file mode 100644
index 000000000..8a1a7dc27
--- /dev/null
+++ b/build/MIT-COMPILED-LICENSE.md
@@ -0,0 +1,14 @@
+Mattermost Compiled License
+(MIT with Trademark Protection)
+
+**Note: this license does not cover source code, for information on source code licensing see LICENSE.txt in the Mattermost source code.
+
+Copyright (c) 2015 Mattermost, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software;
+
+The receiver of the Software will not remove or alter any product identification, trademark, copyright or other notices embedded within or appearing within or on the Software;
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/config/config.json b/config/config.json
index 1dbd05a79..38acee85a 100644
--- a/config/config.json
+++ b/config/config.json
@@ -86,4 +86,4 @@
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
-}
+} \ No newline at end of file
diff --git a/doc/install/prod-ubuntu.md b/doc/install/prod-ubuntu.md
new file mode 100644
index 000000000..866b1bdbe
--- /dev/null
+++ b/doc/install/prod-ubuntu.md
@@ -0,0 +1,187 @@
+# Production Installation on Ubuntu 14.04 LTS
+
+## Install Ubuntu Server 14.04 LTS
+1. Set up 3 machines with Ubuntu 14.04 with 2GB of RAM or more. The servers will be used for the Load Balancer, Mattermost, and Database.
+1. Make sure the system is up to date with the most recent security patches.
+ * ``` sudo apt-get update```
+ * ``` sudo apt-get upgrade```
+
+## Setup Database Server
+1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.1
+1. Install PostgreSQL 9.3+ (or MySQL 5.2+)
+ * ``` sudo apt-get install postgresql postgresql-contrib```
+1. PostgreSQL created a user account called `postgres`. You will need to log into that account with:
+ * ``` sudo -i -u postgres```
+1. You can get a PostgreSQL prompt by typing:
+ * ``` psql```
+1. Create the Mattermost database by typing:
+ * ```postgres=# CREATE DATABASE mattermost;```
+1. Create the Mattermost user by typing:
+ * ```postgres=# CREATE USER mmuser WITH PASSWORD 'mmuser_password';```
+1. Grant the user access to the Mattermost database by typing:
+ * ```postgres=# GRANT ALL PRIVILEGES ON DATABASE mattermost to mmuser;```
+1. You can exit out of PostgreSQL by typing:
+ * ```postgre=# \q```
+1. You can exit the postgres account by typing:
+ * ``` exit```
+
+## Setup Mattermost Server
+1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2
+1. Download the latest Mattermost Server by typing:
+ * ``` wget https://github.com/mattermost/platform/releases/download/v1.0.0/mattermost.tar.gz```
+1. Unzip the Mattermost Server by typing:
+ * ``` tar -xvzf mattermost.tar.gz```
+1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`, in the future we will give guidance for storing under `/opt`.
+1. We have also elected to run the Mattermost Server as the `ubuntu` account for simplicity. We recommend settings up and running the service under a `mattermost` user account with limited permissions.
+1. Create the storage directory for files. We assume you will have attached a large drive for storage of images and files. For this setup we will assume the directory is located at `/mattermost/data`.
+ * Create the directory by typing:
+ * ``` sudo mkdir -p /mattermost/data```
+ * Set the ubuntu account as the directory owner by typing:
+ * ``` sudo chown -R ubuntu /mattermost```
+1. Configure Mattermost Server by editing the config.json file at /home/ubuntu/mattermost/config`
+ * ``` cd ~/mattermost/config```
+ * Edit the file by typing:
+ * ``` vi config.json```
+ * replace `DriverName": "mysql"` with `DriverName": "postgres"`
+ * replace `"DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8"` with `"DataSource": "postgres://mmuser:mmuser_password@10.10.10.1:5432/mattermost?sslmode=disable&connect_timeout=10"`
+ * Optionally you may continue to edit configuration settings in `config.json` or use the System Console described in a later section to finish the configuration.
+1. Test the Mattermost Server
+ * ``` cd ~/mattermost/bin```
+ * Run the Mattermost Server by typing:
+ * ``` ./platform```
+ * You should see a console log like `Server is listening on :8065` letting you know the service is running.
+ * Stop the server for now by typing `ctrl-c`
+1. Setup Mattermost to use the Ubuntu Upstart daemon which handles supervision of the Mattermost process.
+ * ``` sudo touch /etc/init/mattermost.conf```
+ * ``` sudo vi /etc/init/mattermost.conf```
+ * Copy the following lines into `/etc/init/mattermost.conf`
+```
+start on runlevel [2345]
+stop on runlevel [016]
+respawn
+chdir /home/ubuntu/mattermost
+setuid ubuntu
+exec bin/platform
+```
+ * You can manage the process by typing:
+ * ``` sudo start mattermost```
+ * Verify the service is running by typing:
+ * ``` curl http://10.10.10.2:8065```
+ * You should see a page titles *Mattermost - Signup*
+ * You can also stop the process by running the command ` sudo stop mattermost`, but we will skip this step for now.
+
+## Setup Nginx Server
+1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.3
+1. We use Nginx for proxying request to the Mattermost Server. The main benefits are:
+ * SSL termination
+ * http to https redirect
+ * Port mapping :80 to :8065
+ * Standard request logs
+1. Install Nginx on Ubuntu with
+ * ``` sudo apt-get install nginx```
+1. Verify Nginx is running
+ * ``` curl http://10.10.10.3```
+ * You should see a *Welcome to nginx!* page
+1. You can manage Nginx with the following commands
+ * ``` sudo service nginx stop```
+ * ``` sudo service nginx start```
+ * ``` sudo service nginx restart```
+1. Map a FQDN (fully qualified domain name) like **mattermost.example.com** to point to the Nginx server.
+1. Configure Nginx to proxy connections from the internet to the Mattermost Server
+ * Create a configuration for Mattermost
+ * ``` sudo touch /etc/nginx/sites-available/mattermost```
+ * Below is a sample configuration with the minimum settings required to configure Mattermost.
+ *
+ ```
+ server {
+ server_name mattermost.example.com;
+ location / {
+ client_max_body_size 50M;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Frame-Options SAMEORIGIN;
+ proxy_pass http://localhost:8065;
+ }
+ }
+```
+ * Remove the existing file with
+ * ``` sudo rm /etc/nginx/sites-enabled/default```
+ * Link the mattermost config by typing:
+ * ```sudo ln -s /etc/nginx/sites-available/mattermost /etc/nginx/sites-enabled/mattermost```
+ * Restart Nginx by typing:
+ * ``` sudo service nginx restart```
+ * Verify you can see Mattermost thru the proxy by typing:
+ * ``` curl http://localhost```
+ * You should see a page titles *Mattermost - Signup*
+
+## Setup Nginx with SSL (Recommended)
+1. You will need a SSL cert from a certificate authority.
+1. For simplicity we will generate a test certificate.
+ * ``` mkdir ~/cert```
+ * ``` cd ~/cert```
+ * ``` sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mattermost.key -out mattermost.crt```
+ * Input the following info
+```
+ Country Name (2 letter code) [AU]:US
+ State or Province Name (full name) [Some-State]:California
+ Locality Name (eg, city) []:Palo Alto
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:Example LLC
+ Organizational Unit Name (eg, section) []:
+ Common Name (e.g. server FQDN or YOUR name) []:mattermost.example.com
+ Email Address []:admin@mattermost.example.com
+```
+1. Modify the file at `/etc/nginx/sites-available/mattermost` and add the following lines
+ *
+```
+ server {
+ listen 80;
+ server_name mattermost.example.com;
+ return 301 https://$server_name$request_uri;
+ }
+
+ server {
+ listen 443 ssl;
+ server_name mattermost.example.com;
+
+ ssl on;
+ ssl_certificate /home/ubuntu/cert/mattermost.crt;
+ ssl_certificate_key /home/ubuntu/cert/mattermost.key;
+ ssl_session_timeout 5m;
+ ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
+ ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
+ ssl_prefer_server_ciphers on;
+
+ # add to location / above
+ location / {
+ gzip off;
+ proxy_set_header X-Forwarded-Ssl on;
+```
+## Finish Mattermost Server setup
+1. Navigate to https://mattermost.example.com and create a team and user.
+1. The first user in the system is automatically granted the `system_admin` role, which gives you access to the System Console.
+1. From the `town-square` channel click the dropdown and choose the `System Console` option
+1. Update Email Settings. We recommend using an email sending service. The example below assumes AmazonSES.
+ * Set *Send Email Notifications* to true
+ * Set *Require Email Verification* to true
+ * Set *Feedback Name* to `No-Reply`
+ * Set *Feedback Email* to `mattermost@example.com`
+ * Set *SMTP Username* to `AFIADTOVDKDLGERR`
+ * Set *SMTP Password* to `DFKJoiweklsjdflkjOIGHLSDFJewiskdjf`
+ * Set *SMTP Server* to `email-smtp.us-east-1.amazonaws.com`
+ * Set *SMTP Port* to `465`
+ * Set *Connection Security* to `TLS`
+ * Save the Settings
+1. Update File Settings
+ * Change *Local Directory Location* from `./data/` to `/mattermost/data`
+1. Update Log Settings
+ * Set *Log to The Console* to false
+1. Update Rate Limit Settings
+ * Set *Vary By Remote Address* to false
+ * Set *Vary By HTTP Header* to X-Real-IP
+1. Feel free to modify other settings.
+1. Restart the Mattermost Service by typing:
+ * ``` sudo restart mattermost```
diff --git a/docker/0.5/Dockerfile b/docker/0.5/Dockerfile
deleted file mode 100644
index fec69280e..000000000
--- a/docker/0.5/Dockerfile
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-# See License.txt for license information.
-FROM ubuntu:14.04
-
-# Install Dependancies
-RUN apt-get update && apt-get install -y build-essential
-RUN apt-get install -y curl
-RUN curl -sL https://deb.nodesource.com/setup | bash -
-RUN apt-get install -y nodejs
-RUN apt-get install -y ruby-full
-RUN gem install compass
-
-# Postfix
-RUN apt-get install -y postfix
-
-#
-# Install GO
-#
-
-RUN apt-get update && apt-get install -y \
- gcc libc6-dev make git mercurial \
- --no-install-recommends \
- && rm -rf /var/lib/apt/lists/*
-
-ENV GOLANG_VERSION 1.4.2
-
-RUN curl -sSL https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz \
- | tar -v -C /usr/src -xz
-
-RUN cd /usr/src/go/src && ./make.bash --no-clean 2>&1
-
-ENV PATH /usr/src/go/bin:$PATH
-
-RUN mkdir -p /go/src /go/bin && chmod -R 777 /go
-ENV GOPATH /go
-ENV PATH /go/bin:$PATH
-WORKDIR /go
-
-# ---------------------------------------------------------------------------------------------------------------------
-
-#
-# Install SQL
-#
-
-ENV MYSQL_ROOT_PASSWORD=mostest
-ENV MYSQL_USER=mmuser
-ENV MYSQL_PASSWORD=mostest
-ENV MYSQL_DATABASE=mattermost_test
-
-RUN groupadd -r mysql && useradd -r -g mysql mysql
-
-RUN apt-get update && apt-get install -y perl --no-install-recommends && rm -rf /var/lib/apt/lists/*
-
-RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5
-
-ENV MYSQL_MAJOR 5.6
-ENV MYSQL_VERSION 5.6.25
-
-RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list
-
-RUN apt-get update \
- && export DEBIAN_FRONTEND=noninteractive \
- && apt-get -y install mysql-server \
- && rm -rf /var/lib/apt/lists/* \
- && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
-
-RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf
-
-VOLUME /var/lib/mysql
-# ---------------------------------------------------------------------------------------------------------------------
-
-#
-# Install Redis
-#
-
-RUN apt-get update && apt-get install -y wget
-RUN wget http://download.redis.io/redis-stable.tar.gz; \
- tar xvzf redis-stable.tar.gz; \
- cd redis-stable; \
- make install
-
-# ---------------------------------------------------------------------------------------------------------------------
-
-# Copy over files
-ADD https://github.com/mattermost/platform/archive/v0.50.tar.gz /go/src/github.com/mattermost/
-RUN mkdir /go/src/github.com/mattermost/platform && tar -zxvf /go/src/github.com/mattermost/v0.50.tar.gz -C /go/src/github.com/mattermost/platform --strip-components=1
-
-# Insert postfix config
-ADD ./main.cf /etc/postfix/
-
-RUN go get github.com/tools/godep
-RUN cd /go/src/github.com/mattermost/platform; godep restore
-RUN go install github.com/mattermost/platform
-RUN cd /go/src/github.com/mattermost/platform/web/react; npm install
-
-RUN chmod +x /go/src/github.com/mattermost/platform/docker/docker-entry.sh
-ENTRYPOINT /go/src/github.com/mattermost/platform/docker/docker-entry.sh
-
-# Ports
-EXPOSE 80
diff --git a/docker/0.5/Dockerrun.aws.json b/docker/0.5/Dockerrun.aws.json
deleted file mode 100755
index 04c7f4224..000000000
--- a/docker/0.5/Dockerrun.aws.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "AWSEBDockerrunVersion": "1",
- "Image": {
- "Name": "mattermost/platform:helium",
- "Update": "true"
- },
- "Ports": [
- {
- "ContainerPort": "80"
- }
- ],
- "Logging": "/var/log/"
-}
diff --git a/docker/0.5/docker-entry.sh b/docker/0.5/docker-entry.sh
deleted file mode 100755
index cfa589041..000000000
--- a/docker/0.5/docker-entry.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/bin/bash
-# Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-# See License.txt for license information.
-
-mkdir -p web/static/js
-
-echo "127.0.0.1 dockerhost" >> /etc/hosts
-/etc/init.d/networking restart
-
-echo configuring mysql
-
-# SQL!!!
-set -e
-
-get_option () {
- local section=$1
- local option=$2
- local default=$3
- ret=$(my_print_defaults $section | grep '^--'${option}'=' | cut -d= -f2-)
- [ -z $ret ] && ret=$default
- echo $ret
-}
-
-
-# Get config
-DATADIR="$("mysqld" --verbose --help 2>/dev/null | awk '$1 == "datadir" { print $2; exit }')"
-SOCKET=$(get_option mysqld socket "$DATADIR/mysql.sock")
-PIDFILE=$(get_option mysqld pid-file "/var/run/mysqld/mysqld.pid")
-
-if [ ! -d "$DATADIR/mysql" ]; then
- if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then
- echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
- echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?'
- exit 1
- fi
-
- mkdir -p "$DATADIR"
- chown -R mysql:mysql "$DATADIR"
-
- echo 'Running mysql_install_db'
- mysql_install_db --user=mysql --datadir="$DATADIR" --rpm --keep-my-cnf
- echo 'Finished mysql_install_db'
-
- mysqld --user=mysql --datadir="$DATADIR" --skip-networking &
- for i in $(seq 30 -1 0); do
- [ -S "$SOCKET" ] && break
- echo 'MySQL init process in progress...'
- sleep 1
- done
- if [ $i = 0 ]; then
- echo >&2 'MySQL init process failed.'
- exit 1
- fi
-
- # These statements _must_ be on individual lines, and _must_ end with
- # semicolons (no line breaks or comments are permitted).
- # TODO proper SQL escaping on ALL the things D:
-
- tempSqlFile=$(mktemp /tmp/mysql-first-time.XXXXXX.sql)
- cat > "$tempSqlFile" <<-EOSQL
- -- What's done in this file shouldn't be replicated
- -- or products like mysql-fabric won't work
- SET @@SESSION.SQL_LOG_BIN=0;
-
- DELETE FROM mysql.user ;
- CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
- GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
- DROP DATABASE IF EXISTS test ;
- EOSQL
-
- if [ "$MYSQL_DATABASE" ]; then
- echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" >> "$tempSqlFile"
- fi
-
- if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
- echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" >> "$tempSqlFile"
-
- if [ "$MYSQL_DATABASE" ]; then
- echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" >> "$tempSqlFile"
- fi
- fi
-
- echo 'FLUSH PRIVILEGES ;' >> "$tempSqlFile"
-
- mysql -uroot < "$tempSqlFile"
-
- rm -f "$tempSqlFile"
- kill $(cat $PIDFILE)
- for i in $(seq 30 -1 0); do
- [ -f "$PIDFILE" ] || break
- echo 'MySQL init process in progress...'
- sleep 1
- done
- if [ $i = 0 ]; then
- echo >&2 'MySQL hangs during init process.'
- exit 1
- fi
- echo 'MySQL init process done. Ready for start up.'
-fi
-
-chown -R mysql:mysql "$DATADIR"
-
-mysqld &
-
-sleep 5
-
-# ------------------------
-
-echo starting postfix
-/etc/init.d/postfix restart
-
-echo starting redis
-redis-server &
-
-echo starting react processor
-cd /go/src/github.com/mattermost/platform/web/react && npm start &
-
-echo starting go web server
-cd /go/src/github.com/mattermost/platform/; go run mattermost.go -config=config_docker.json &
-
-echo starting compass watch
-cd /go/src/github.com/mattermost/platform/web/sass-files && compass watch
diff --git a/docker/0.5/main.cf b/docker/0.5/main.cf
deleted file mode 100644
index ed97d37ef..000000000
--- a/docker/0.5/main.cf
+++ /dev/null
@@ -1,28 +0,0 @@
-myorigin = mattermost.com
-myhostname = mattermost.com
-
-smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
-biff = no
-
-append_dot_mydomain = no
-
-readme_directory = no
-
-# TLS parameters
-smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
-smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
-smtpd_use_tls=no
-smtp_use_tls=no
-smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
-smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
-
-smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
-alias_maps = hash:/etc/aliases
-alias_database = hash:/etc/aliases
-mydestination = localhost, localhost.localdomain, localhost
-relayhost =
-mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
-mailbox_size_limit = 0
-recipient_delimiter = +
-inet_interfaces = all
-inet_protocols = all
diff --git a/docker/0.6/Dockerrun.aws.zip b/docker/0.6/Dockerrun.aws.zip
deleted file mode 100644
index 342c1549a..000000000
--- a/docker/0.6/Dockerrun.aws.zip
+++ /dev/null
Binary files differ
diff --git a/docker/0.6/config_docker.json b/docker/0.6/config_docker.json
deleted file mode 100644
index b1c72c4bd..000000000
--- a/docker/0.6/config_docker.json
+++ /dev/null
@@ -1,99 +0,0 @@
-{
- "LogSettings": {
- "ConsoleEnable": false,
- "ConsoleLevel": "DEBUG",
- "FileEnable": true,
- "FileLevel": "INFO",
- "FileFormat": "",
- "FileLocation": ""
- },
- "ServiceSettings": {
- "SiteName": "Mattermost",
- "Mode" : "dev",
- "AllowTesting" : false,
- "UseSSL": false,
- "Port": "80",
- "Version": "developer",
- "Shards": {
- },
- "InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
- "PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
- "ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
- "AnalyticsUrl": "",
- "UseLocalStorage": true,
- "StorageDirectory": "/mattermost/data/",
- "AllowedLoginAttempts": 10
- },
- "SSOSettings": {
- "gitlab": {
- "Allow": false,
- "Secret" : "",
- "Id": "",
- "AuthEndpoint": "",
- "TokenEndpoint": "",
- "UserApiEndpoint": ""
- }
- },
- "SqlSettings": {
- "DriverName": "mysql",
- "DataSource": "mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8",
- "DataSourceReplicas": ["mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8"],
- "MaxIdleConns": 10,
- "MaxOpenConns": 10,
- "Trace": false,
- "AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS"
- },
- "AWSSettings": {
- "S3AccessKeyId": "",
- "S3SecretAccessKey": "",
- "S3Bucket": "",
- "S3Region": ""
- },
- "ImageSettings": {
- "ThumbnailWidth": 120,
- "ThumbnailHeight": 100,
- "PreviewWidth": 1024,
- "PreviewHeight": 0,
- "ProfileWidth": 128,
- "ProfileHeight": 128,
- "InitialFont": "luximbi.ttf"
- },
- "EmailSettings": {
- "ByPassEmail" : true,
- "SMTPUsername": "",
- "SMTPPassword": "",
- "SMTPServer": "",
- "UseTLS": false,
- "UseStartTLS": false,
- "FeedbackEmail": "",
- "FeedbackName": "",
- "ApplePushServer": "",
- "ApplePushCertPublic": "",
- "ApplePushCertPrivate": ""
- },
- "RateLimitSettings": {
- "UseRateLimiter": true,
- "PerSec": 10,
- "MemoryStoreSize": 10000,
- "VaryByRemoteAddr": true,
- "VaryByHeader": ""
- },
- "PrivacySettings": {
- "ShowEmailAddress": true,
- "ShowPhoneNumber": true,
- "ShowSkypeId": true,
- "ShowFullName": true
- },
- "TeamSettings": {
- "MaxUsersPerTeam": 150,
- "AllowPublicLink": true,
- "AllowValetDefault": false,
- "TermsLink": "/static/help/configure_links.html",
- "PrivacyLink": "/static/help/configure_links.html",
- "AboutLink": "/static/help/configure_links.html",
- "HelpLink": "/static/help/configure_links.html",
- "ReportProblemLink": "/static/help/configure_links.html",
- "TourLink": "/static/help/configure_links.html",
- "DefaultThemeColor": "#2389D7"
- }
-}
diff --git a/docker/0.6/Dockerfile b/docker/1.0/Dockerfile
index 35ba0bd7e..8eeef8fb7 100644
--- a/docker/0.6/Dockerfile
+++ b/docker/1.0/Dockerfile
@@ -13,8 +13,6 @@ ENV MYSQL_DATABASE=mattermost_test
RUN groupadd -r mysql && useradd -r -g mysql mysql
-RUN apt-get update && apt-get install -y perl --no-install-recommends && rm -rf /var/lib/apt/lists/*
-
RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5
ENV MYSQL_MAJOR 5.6
@@ -24,7 +22,7 @@ RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
- && apt-get -y install mysql-server \
+ && apt-get -y install perl wget mysql-server \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
@@ -36,8 +34,8 @@ VOLUME /var/lib/mysql
WORKDIR /mattermost
# Copy over files
-ADD https://github.com/mattermost/platform/releases/download/v0.6.0/mattermost.tar.gz /
-RUN tar -zxvf /mattermost.tar.gz --strip-components=1
+ADD https://github.com/mattermost/platform/releases/download/v1.0.0-rc2/mattermost.tar.gz /
+RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz
ADD config_docker.json /
ADD docker-entry.sh /
diff --git a/docker/1.0/Dockerrun.aws.zip b/docker/1.0/Dockerrun.aws.zip
new file mode 100644
index 000000000..8c2c16e10
--- /dev/null
+++ b/docker/1.0/Dockerrun.aws.zip
Binary files differ
diff --git a/docker/0.6/Dockerrun.aws/.ebextensions/01_files.config b/docker/1.0/Dockerrun.aws/.ebextensions/01_files.config
index 7f40a8b34..7f40a8b34 100644
--- a/docker/0.6/Dockerrun.aws/.ebextensions/01_files.config
+++ b/docker/1.0/Dockerrun.aws/.ebextensions/01_files.config
diff --git a/docker/0.6/Dockerrun.aws/Dockerrun.aws.json b/docker/1.0/Dockerrun.aws/Dockerrun.aws.json
index f6f7cf726..9fdded15e 100755
--- a/docker/0.6/Dockerrun.aws/Dockerrun.aws.json
+++ b/docker/1.0/Dockerrun.aws/Dockerrun.aws.json
@@ -1,7 +1,7 @@
{
"AWSEBDockerrunVersion": "1",
"Image": {
- "Name": "mattermost/platform:0.6",
+ "Name": "mattermost/platform:1.0",
"Update": "true"
},
"Ports": [
diff --git a/docker/1.0/config_docker.json b/docker/1.0/config_docker.json
new file mode 100644
index 000000000..806071376
--- /dev/null
+++ b/docker/1.0/config_docker.json
@@ -0,0 +1,89 @@
+{
+ "ServiceSettings": {
+ "ListenAddress": ":80",
+ "MaximumLoginAttempts": 10,
+ "SegmentDeveloperKey": "",
+ "GoogleDeveloperKey": "",
+ "EnableOAuthServiceProvider": false,
+ "EnableIncomingWebhooks": false,
+ "EnableTesting": false
+ },
+ "TeamSettings": {
+ "SiteName": "Mattermost",
+ "MaxUsersPerTeam": 50,
+ "EnableTeamCreation": true,
+ "EnableUserCreation": true,
+ "RestrictCreationToDomains": ""
+ },
+ "SqlSettings": {
+ "DriverName": "mysql",
+ "DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8",
+ "DataSourceReplicas": [],
+ "MaxIdleConns": 10,
+ "MaxOpenConns": 10,
+ "Trace": false,
+ "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QVg"
+ },
+ "LogSettings": {
+ "EnableConsole": false,
+ "ConsoleLevel": "INFO",
+ "EnableFile": true,
+ "FileLevel": "INFO",
+ "FileFormat": "",
+ "FileLocation": ""
+ },
+ "FileSettings": {
+ "DriverName": "local",
+ "Directory": "/mattermost/data/",
+ "EnablePublicLink": true,
+ "PublicLinkSalt": "A705AklYF8MFDOfcwh3I488G8vtLlVip",
+ "ThumbnailWidth": 120,
+ "ThumbnailHeight": 100,
+ "PreviewWidth": 1024,
+ "PreviewHeight": 0,
+ "ProfileWidth": 128,
+ "ProfileHeight": 128,
+ "InitialFont": "luximbi.ttf",
+ "AmazonS3AccessKeyId": "",
+ "AmazonS3SecretAccessKey": "",
+ "AmazonS3Bucket": "",
+ "AmazonS3Region": ""
+ },
+ "EmailSettings": {
+ "EnableSignUpWithEmail": true,
+ "SendEmailNotifications": false,
+ "RequireEmailVerification": false,
+ "FeedbackName": "",
+ "FeedbackEmail": "",
+ "SMTPUsername": "",
+ "SMTPPassword": "",
+ "SMTPServer": "",
+ "SMTPPort": "",
+ "ConnectionSecurity": "",
+ "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS",
+ "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL",
+ "ApplePushServer": "",
+ "ApplePushCertPublic": "",
+ "ApplePushCertPrivate": ""
+ },
+ "RateLimitSettings": {
+ "EnableRateLimiter": true,
+ "PerSec": 10,
+ "MemoryStoreSize": 10000,
+ "VaryByRemoteAddr": true,
+ "VaryByHeader": ""
+ },
+ "PrivacySettings": {
+ "ShowEmailAddress": true,
+ "ShowFullName": true
+ },
+ "GitLabSettings": {
+ "Enable": false,
+ "Secret": "",
+ "Id": "",
+ "Scope": "",
+ "AuthEndpoint": "",
+ "TokenEndpoint": "",
+ "UserApiEndpoint": ""
+ }
+} \ No newline at end of file
diff --git a/docker/0.6/docker-entry.sh b/docker/1.0/docker-entry.sh
index ce9f91c40..ce9f91c40 100755
--- a/docker/0.6/docker-entry.sh
+++ b/docker/1.0/docker-entry.sh
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index 0001eeb0a..733267f74 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -22,7 +22,7 @@
"MaxIdleConns": 10,
"MaxOpenConns": 10,
"Trace": false,
- "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"
+ "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QVg"
},
"LogSettings": {
"EnableConsole": false,
@@ -36,7 +36,7 @@
"DriverName": "local",
"Directory": "/mattermost/data/",
"EnablePublicLink": true,
- "PublicLinkSalt": "LhaAWC6lYEKHTkBKsvyXNIOfUIT37AX",
+ "PublicLinkSalt": "A705AklYF8MFDOfcwh3I488G8vtLlVip",
"ThumbnailWidth": 120,
"ThumbnailHeight": 100,
"PreviewWidth": 1024,
@@ -60,8 +60,8 @@
"SMTPServer": "",
"SMTPPort": "",
"ConnectionSecurity": "",
- "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo",
- "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5e",
+ "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS",
+ "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL",
"ApplePushServer": "",
"ApplePushCertPublic": "",
"ApplePushCertPrivate": ""
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index 0001eeb0a..733267f74 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -22,7 +22,7 @@
"MaxIdleConns": 10,
"MaxOpenConns": 10,
"Trace": false,
- "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"
+ "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QVg"
},
"LogSettings": {
"EnableConsole": false,
@@ -36,7 +36,7 @@
"DriverName": "local",
"Directory": "/mattermost/data/",
"EnablePublicLink": true,
- "PublicLinkSalt": "LhaAWC6lYEKHTkBKsvyXNIOfUIT37AX",
+ "PublicLinkSalt": "A705AklYF8MFDOfcwh3I488G8vtLlVip",
"ThumbnailWidth": 120,
"ThumbnailHeight": 100,
"PreviewWidth": 1024,
@@ -60,8 +60,8 @@
"SMTPServer": "",
"SMTPPort": "",
"ConnectionSecurity": "",
- "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo",
- "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5e",
+ "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS",
+ "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL",
"ApplePushServer": "",
"ApplePushCertPublic": "",
"ApplePushCertPrivate": ""
diff --git a/model/config.go b/model/config.go
index 853e2bbc0..5d822e263 100644
--- a/model/config.go
+++ b/model/config.go
@@ -16,6 +16,9 @@ const (
IMAGE_DRIVER_LOCAL = "local"
IMAGE_DRIVER_S3 = "amazons3"
+ DATABASE_DRIVER_MYSQL = "mysql"
+ DATABASE_DRIVER_POSTGRES = "postgres"
+
SERVICE_GITLAB = "gitlab"
)
@@ -156,3 +159,92 @@ func ConfigFromJson(data io.Reader) *Config {
return nil
}
}
+
+func (o *Config) IsValid() *AppError {
+
+ if o.ServiceSettings.MaximumLoginAttempts <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum login attempts for service settings. Must be a positive number.", "")
+ }
+
+ if len(o.ServiceSettings.ListenAddress) == 0 {
+ return NewAppError("Config.IsValid", "Invalid listen address for service settings Must be set.", "")
+ }
+
+ if o.TeamSettings.MaxUsersPerTeam <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum users per team for team settings. Must be a positive number.", "")
+ }
+
+ if len(o.SqlSettings.AtRestEncryptKey) != 32 {
+ return NewAppError("Config.IsValid", "Invalid at rest encrypt key for SQL settings. Must be 32 chars.", "")
+ }
+
+ if !(o.SqlSettings.DriverName == DATABASE_DRIVER_MYSQL || o.SqlSettings.DriverName == DATABASE_DRIVER_POSTGRES) {
+ return NewAppError("Config.IsValid", "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'", "")
+ }
+
+ if o.SqlSettings.MaxIdleConns <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum idle connection for SQL settings. Must be a positive number.", "")
+ }
+
+ if len(o.SqlSettings.DataSource) == 0 {
+ return NewAppError("Config.IsValid", "Invalid data source for SQL settings. Must be set.", "")
+ }
+
+ if o.SqlSettings.MaxOpenConns <= 0 {
+ return NewAppError("Config.IsValid", "Invalid maximum open connection for SQL settings. Must be a positive number.", "")
+ }
+
+ if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) {
+ return NewAppError("Config.IsValid", "Invalid driver name for file settings. Must be 'local' or 'amazons3'", "")
+ }
+
+ if o.FileSettings.PreviewHeight < 0 {
+ return NewAppError("Config.IsValid", "Invalid preview height for file settings. Must be a zero or positive number.", "")
+ }
+
+ if o.FileSettings.PreviewWidth <= 0 {
+ return NewAppError("Config.IsValid", "Invalid preview width for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ProfileHeight <= 0 {
+ return NewAppError("Config.IsValid", "Invalid profile height for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ProfileWidth <= 0 {
+ return NewAppError("Config.IsValid", "Invalid profile width for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ThumbnailHeight <= 0 {
+ return NewAppError("Config.IsValid", "Invalid thumbnail height for file settings. Must be a positive number.", "")
+ }
+
+ if o.FileSettings.ThumbnailHeight <= 0 {
+ return NewAppError("Config.IsValid", "Invalid thumbnail width for file settings. Must be a positive number.", "")
+ }
+
+ if len(o.FileSettings.PublicLinkSalt) != 32 {
+ return NewAppError("Config.IsValid", "Invalid public link salt for file settings. Must be 32 chars.", "")
+ }
+
+ if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) {
+ return NewAppError("Config.IsValid", "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'", "")
+ }
+
+ if len(o.EmailSettings.InviteSalt) != 32 {
+ return NewAppError("Config.IsValid", "Invalid invite salt for email settings. Must be 32 chars.", "")
+ }
+
+ if len(o.EmailSettings.PasswordResetSalt) != 32 {
+ return NewAppError("Config.IsValid", "Invalid password reset salt for email settings. Must be 32 chars.", "")
+ }
+
+ if o.RateLimitSettings.MemoryStoreSize <= 0 {
+ return NewAppError("Config.IsValid", "Invalid memory store size for rate limit settings. Must be a positive number", "")
+ }
+
+ if o.RateLimitSettings.PerSec <= 0 {
+ return NewAppError("Config.IsValid", "Invalid per sec for rate limit settings. Must be a positive number", "")
+ }
+
+ return nil
+}
diff --git a/model/user.go b/model/user.go
index 5cb774478..d8000a7e2 100644
--- a/model/user.go
+++ b/model/user.go
@@ -304,10 +304,14 @@ func isValidRole(role string) bool {
return false
}
+// Make sure you acually want to use this function. In context.go there are functions to check permssions
+// This function should not be used to check permissions.
func (u *User) IsInRole(inRole string) bool {
return IsInRole(u.Roles, inRole)
}
+// Make sure you acually want to use this function. In context.go there are functions to check permssions
+// This function should not be used to check permissions.
func IsInRole(userRoles string, inRole string) bool {
roles := strings.Split(userRoles, " ")
diff --git a/model/version.go b/model/version.go
index 8f0c76ebe..233fc3747 100644
--- a/model/version.go
+++ b/model/version.go
@@ -12,7 +12,7 @@ import (
// It should be maitained in chronological order with most current
// release at the front of the list.
var versions = []string{
- "0.8.0",
+ "1.0.0",
"0.7.1",
"0.7.0",
"0.6.0",
diff --git a/model/version_test.go b/model/version_test.go
index da40006be..24dbedaa6 100644
--- a/model/version_test.go
+++ b/model/version_test.go
@@ -36,7 +36,7 @@ func TestSplitVersion(t *testing.T) {
}
func TestGetPreviousVersion(t *testing.T) {
- if major, minor := GetPreviousVersion("0.8.0"); major != 0 || minor != 7 {
+ if major, minor := GetPreviousVersion("1.0.0"); major != 0 || minor != 7 {
t.Fatal(major, minor)
}
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index 877246fc3..cb686090e 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -583,7 +583,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto
var query string
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
query = `UPDATE
ChannelMembers
SET
@@ -597,7 +597,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto
Channels.Id = ChannelMembers.ChannelId
AND UserId = :UserId
AND ChannelId = :ChannelId`
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
query = `UPDATE
ChannelMembers, Channels
SET
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index 21e8e9d00..668f45fbb 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -418,7 +418,7 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht
var posts []*model.Post
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
// Parse text for wildcards
if wildcard, err := regexp.Compile("\\*($| )"); err == nil {
@@ -451,7 +451,7 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht
result.Err = model.NewAppError("SqlPostStore.Search", "We encounted an error while searching for posts", "teamId="+teamId+", err="+err.Error())
}
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
searchQuery := fmt.Sprintf(`SELECT
*
FROM
diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go
index bc1cb2c2c..257054033 100644
--- a/store/sql_post_store_test.go
+++ b/store/sql_post_store_test.go
@@ -530,13 +530,6 @@ func TestPostStoreSearch(t *testing.T) {
t.Fatal("returned wrong serach result")
}
- // if utils.Cfg.SqlSettings.DriverName == "mysql" {
- // r2 := (<-store.Post().Search(teamId, userId, "new york", false)).Data.(*model.PostList)
- // if len(r2.Order) >= 1 && r2.Order[0] != o2.Id {
- // t.Fatal("returned wrong serach result")
- // }
- // }
-
r3 := (<-store.Post().Search(teamId, userId, "new", false)).Data.(*model.PostList)
if len(r3.Order) != 2 && r3.Order[0] != o1.Id {
t.Fatal("returned wrong serach result")
diff --git a/store/sql_store.go b/store/sql_store.go
index 6dcf2e8cd..3ef9cfbc4 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -160,9 +160,9 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
if driver == "sqlite3" {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}}
- } else if driver == "mysql" {
+ } else if driver == model.DATABASE_DRIVER_MYSQL {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"}}
- } else if driver == "postgres" {
+ } else if driver == model.DATABASE_DRIVER_POSTGRES {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}}
} else {
l4g.Critical("Failed to create dialect specific driver")
@@ -183,7 +183,7 @@ func (ss SqlStore) GetCurrentSchemaVersion() string {
}
func (ss SqlStore) DoesTableExist(tableName string) bool {
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
count, err := ss.GetMaster().SelectInt(
`SELECT count(relname) FROM pg_class WHERE relname=$1`,
strings.ToLower(tableName),
@@ -197,7 +197,7 @@ func (ss SqlStore) DoesTableExist(tableName string) bool {
return count > 0
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
count, err := ss.GetMaster().SelectInt(
`SELECT
@@ -228,7 +228,7 @@ func (ss SqlStore) DoesTableExist(tableName string) bool {
}
func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool {
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
count, err := ss.GetMaster().SelectInt(
`SELECT COUNT(0)
FROM pg_attribute
@@ -251,7 +251,7 @@ func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool {
return count > 0
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
count, err := ss.GetMaster().SelectInt(
`SELECT
@@ -288,7 +288,7 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string,
return false
}
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'")
if err != nil {
l4g.Critical("Failed to create column %v", err)
@@ -298,7 +298,7 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string,
return true
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'")
if err != nil {
l4g.Critical("Failed to create column %v", err)
@@ -334,7 +334,7 @@ func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) boo
// func (ss SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
// // XXX TODO FIXME this should be removed after 0.6.0
-// if utils.Cfg.SqlSettings.DriverName == "postgres" {
+// if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
// return false
// }
@@ -363,7 +363,7 @@ func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName st
func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, fullText bool) {
- if utils.Cfg.SqlSettings.DriverName == "postgres" {
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
_, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
// It should fail if the index does not exist
if err == nil {
@@ -383,7 +383,7 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
time.Sleep(time.Second)
panic("Failed to create index " + err.Error())
}
- } else if utils.Cfg.SqlSettings.DriverName == "mysql" {
+ } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
if err != nil {
diff --git a/utils/config.go b/utils/config.go
index 5d786699b..3218211e3 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -150,7 +150,12 @@ func LoadConfig(fileName string) {
CfgFileName = fileName
}
+ if err := config.IsValid(); err != nil {
+ panic("Error validating config file=" + fileName + ", err=" + err.Message)
+ }
+
configureLog(&config.LogSettings)
+ TestConnection(&config)
Cfg = &config
SanitizeOptions = getSanitizeOptions(Cfg)
diff --git a/utils/mail.go b/utils/mail.go
index dd975155d..f6fe1ce00 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -65,13 +65,34 @@ func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.Ap
return c, nil
}
+func TestConnection(config *model.Config) {
+ if !config.EmailSettings.SendEmailNotifications {
+ return
+ }
+
+ conn, err1 := connectToSMTPServer(config)
+ if err1 != nil {
+ l4g.Error("SMTP server settings do not appear to be configured properly err=%v details=%v", err1.Message, err1.DetailedError)
+ return
+ }
+ defer conn.Close()
+
+ c, err2 := newSMTPClient(conn, config)
+ if err2 != nil {
+ l4g.Error("SMTP connection settings do not appear to be configured properly err=%v details=%v", err2.Message, err2.DetailedError)
+ return
+ }
+ defer c.Quit()
+ defer c.Close()
+}
+
func SendMail(to, subject, body string) *model.AppError {
return SendMailUsingConfig(to, subject, body, Cfg)
}
func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.AppError {
- if !config.EmailSettings.SendEmailNotifications {
+ if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 {
return nil
}
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 3b5ad2a1a..762a4ab26 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -288,7 +288,7 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackName'
>
- {'Feedback Name:'}
+ {'Notification Display Name:'}
</label>
<div className='col-sm-8'>
<input
@@ -310,7 +310,7 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackEmail'
>
- {'Feedback Email:'}
+ {'Notification Email Address:'}
</label>
<div className='col-sm-8'>
<input
diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx
index 1e10c5592..759892ad3 100644
--- a/web/react/components/admin_console/gitlab_settings.jsx
+++ b/web/react/components/admin_console/gitlab_settings.jsx
@@ -12,7 +12,7 @@ export default class GitLabSettings extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
- Allow: this.props.config.GitLabSettings.Allow,
+ Enable: this.props.config.GitLabSettings.Enable,
saveNeeded: false,
serverError: null
};
@@ -21,12 +21,12 @@ export default class GitLabSettings extends React.Component {
handleChange(action) {
var s = {saveNeeded: true, serverError: this.state.serverError};
- if (action === 'AllowTrue') {
- s.Allow = true;
+ if (action === 'EnableTrue') {
+ s.Enable = true;
}
- if (action === 'AllowFalse') {
- s.Allow = false;
+ if (action === 'EnableFalse') {
+ s.Enable = false;
}
this.setState(s);
@@ -37,10 +37,9 @@ export default class GitLabSettings extends React.Component {
$('#save-button').button('loading');
var config = this.props.config;
- config.GitLabSettings.Allow = React.findDOMNode(this.refs.Allow).checked;
+ config.GitLabSettings.Enable = React.findDOMNode(this.refs.Enable).checked;
config.GitLabSettings.Secret = React.findDOMNode(this.refs.Secret).value.trim();
config.GitLabSettings.Id = React.findDOMNode(this.refs.Id).value.trim();
- config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim();
config.GitLabSettings.AuthEndpoint = React.findDOMNode(this.refs.AuthEndpoint).value.trim();
config.GitLabSettings.TokenEndpoint = React.findDOMNode(this.refs.TokenEndpoint).value.trim();
config.GitLabSettings.UserApiEndpoint = React.findDOMNode(this.refs.UserApiEndpoint).value.trim();
@@ -88,7 +87,7 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Allow'
+ htmlFor='Enable'
>
{'Enable Sign Up With GitLab: '}
</label>
@@ -96,21 +95,21 @@ export default class GitLabSettings extends React.Component {
<label className='radio-inline'>
<input
type='radio'
- name='Allow'
+ name='Enable'
value='true'
- ref='Allow'
- defaultChecked={this.props.config.GitLabSettings.Allow}
- onChange={this.handleChange.bind(this, 'AllowTrue')}
+ ref='Enable'
+ defaultChecked={this.props.config.GitLabSettings.Enable}
+ onChange={this.handleChange.bind(this, 'EnableTrue')}
/>
{'true'}
</label>
<label className='radio-inline'>
<input
type='radio'
- name='Allow'
+ name='Enable'
value='false'
- defaultChecked={!this.props.config.GitLabSettings.Allow}
- onChange={this.handleChange.bind(this, 'AllowFalse')}
+ defaultChecked={!this.props.config.GitLabSettings.Enable}
+ onChange={this.handleChange.bind(this, 'EnableFalse')}
/>
{'false'}
</label>
@@ -121,28 +120,6 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Secret'
- >
- {'Secret:'}
- </label>
- <div className='col-sm-8'>
- <input
- type='text'
- className='form-control'
- id='Secret'
- ref='Secret'
- placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
- defaultValue={this.props.config.GitLabSettings.Secret}
- onChange={this.handleChange}
- disabled={!this.state.Allow}
- />
- <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p>
- </div>
- </div>
-
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
htmlFor='Id'
>
{'Id:'}
@@ -156,7 +133,7 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
defaultValue={this.props.config.GitLabSettings.Id}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
<p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab'}</p>
</div>
@@ -165,22 +142,22 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Scope'
+ htmlFor='Secret'
>
- {'Scope:'}
+ {'Secret:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
- id='Scope'
- ref='Scope'
- placeholder='Not currently used by GitLab. Please leave blank'
- defaultValue={this.props.config.GitLabSettings.Scope}
+ id='Secret'
+ ref='Secret'
+ placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ defaultValue={this.props.config.GitLabSettings.Secret}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
+ <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p>
</div>
</div>
@@ -200,9 +177,9 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex ""'
defaultValue={this.props.config.GitLabSettings.AuthEndpoint}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize).'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -222,9 +199,9 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex ""'
defaultValue={this.props.config.GitLabSettings.TokenEndpoint}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token.'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -244,9 +221,9 @@ export default class GitLabSettings extends React.Component {
placeholder='Ex ""'
defaultValue={this.props.config.GitLabSettings.UserApiEndpoint}
onChange={this.handleChange}
- disabled={!this.state.Allow}
+ disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user.'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -272,6 +249,30 @@ export default class GitLabSettings extends React.Component {
}
}
+
+//config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim();
+// <div className='form-group'>
+// <label
+// className='control-label col-sm-4'
+// htmlFor='Scope'
+// >
+// {'Scope:'}
+// </label>
+// <div className='col-sm-8'>
+// <input
+// type='text'
+// className='form-control'
+// id='Scope'
+// ref='Scope'
+// placeholder='Not currently used by GitLab. Please leave blank'
+// defaultValue={this.props.config.GitLabSettings.Scope}
+// onChange={this.handleChange}
+// disabled={!this.state.Allow}
+// />
+// <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
+// </div>
+// </div>
+
GitLabSettings.propTypes = {
config: React.PropTypes.object
};
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 4c01d2c43..57a78a0d4 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -62,7 +62,7 @@ export default class NavbarDropdown extends React.Component {
if (currentUser != null) {
isAdmin = Utils.isAdmin(currentUser.roles);
- isSystemAdmin = Utils.isInRole(currentUser.roles, 'system_admin');
+ isSystemAdmin = Utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx
index fad27b355..ccb26cc58 100644
--- a/web/react/components/setting_upload.jsx
+++ b/web/react/components/setting_upload.jsx
@@ -7,11 +7,11 @@ export default class SettingsUpload extends React.Component {
this.doFileSelect = this.doFileSelect.bind(this);
this.doSubmit = this.doSubmit.bind(this);
- this.onFileSelect = this.onFileSelect.bind(this);
this.state = {
clientError: this.props.clientError,
- serverError: this.props.serverError
+ serverError: this.props.serverError,
+ filename: ''
};
}
@@ -24,9 +24,14 @@ export default class SettingsUpload extends React.Component {
doFileSelect(e) {
e.preventDefault();
+ var filename = $(e.target).val();
+ if (filename.substring(3, 11) === 'fakepath') {
+ filename = filename.substring(12);
+ }
this.setState({
clientError: '',
- serverError: ''
+ serverError: '',
+ filename
});
}
@@ -40,28 +45,28 @@ export default class SettingsUpload extends React.Component {
}
}
- onFileSelect(e) {
- var filename = $(e.target).val();
- if (filename.substring(3, 11) === 'fakepath') {
- filename = filename.substring(12);
- }
- $(e.target).closest('li').find('.file-status').addClass('hide');
- $(e.target).closest('li').find('.file-name').removeClass('hide').html(filename);
- }
-
render() {
- var clientError = null;
+ let clientError = null;
if (this.state.clientError) {
clientError = (
<div className='file-status'>{this.state.clientError}</div>
);
}
- var serverError = null;
+ let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='file-status'>{this.state.serverError}</div>
);
}
+ let fileNameText = null;
+ let submitButtonClass = 'btn btn-sm btn-primary disabled';
+ if (this.state.filename) {
+ fileNameText = (
+ <div className='file-status file-name'>{this.state.filename}</div>
+ );
+ submitButtonClass = 'btn btn-sm btn-primary';
+ }
+
return (
<ul className='section-max'>
<li className='col-sm-12 section-title'>{this.props.title}</li>
@@ -70,21 +75,21 @@ export default class SettingsUpload extends React.Component {
<ul className='setting-list'>
<li className='setting-list-item'>
<span className='btn btn-sm btn-primary btn-file sel-btn'>
- Select file
+ {'Select file'}
<input
ref='uploadinput'
accept={this.props.fileTypesAccepted}
type='file'
- onChange={this.onFileSelect}
+ onChange={this.doFileSelect}
/>
</span>
<a
- className={'btn btn-sm btn-primary'}
+ className={submitButtonClass}
onClick={this.doSubmit}
>
- Import
+ {'Import'}
</a>
- <div className='file-status file-name hide'></div>
+ {fileNameText}
{serverError}
{clientError}
</li>
@@ -102,4 +107,4 @@ SettingsUpload.propTypes = {
clientError: React.PropTypes.string,
serverError: React.PropTypes.string,
helpText: React.PropTypes.object
-};
+}; \ No newline at end of file
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index e63418ae8..708cd04cb 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -33,7 +33,7 @@ export default class SidebarRight extends React.Component {
if (!this.plScrolledToBottom) {
$('.top-visible-post')[0].scrollIntoView();
} else {
- var postHolder = $('.post-list-holder-by-time');
+ var postHolder = $('.post-list-holder-by-time').not('.inactive');
postHolder.scrollTop(postHolder[0].scrollHeight);
}
}
@@ -51,7 +51,7 @@ export default class SidebarRight extends React.Component {
}
}
render() {
- var postHolder = $('.post-list-holder-by-time');
+ var postHolder = $('.post-list-holder-by-time').not('.inactive');
const position = postHolder.scrollTop() + postHolder.height() + 14;
const bottom = postHolder[0].scrollHeight;
this.plScrolledToBottom = position >= bottom;
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index f1341d9d7..2df2c8ffd 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -26,11 +26,14 @@ export default class SidebarRightMenu extends React.Component {
var inviteLink = '';
var teamSettingsLink = '';
var manageLink = '';
+ var consoleLink = '';
var currentUser = UserStore.getCurrentUser();
var isAdmin = false;
+ var isSystemAdmin = false;
if (currentUser != null) {
isAdmin = utils.isAdmin(currentUser.roles);
+ isSystemAdmin = utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
@@ -77,6 +80,17 @@ export default class SidebarRightMenu extends React.Component {
);
}
+ if (isSystemAdmin) {
+ consoleLink = (
+ <li>
+ <a
+ href='/admin_console'
+ >
+ <i className='glyphicon glyphicon-wrench'></i>System Console</a>
+ </li>
+ );
+ }
+
var siteName = '';
if (global.window.config.SiteName != null) {
siteName = global.window.config.SiteName;
@@ -107,6 +121,7 @@ export default class SidebarRightMenu extends React.Component {
{inviteLink}
{teamLink}
{manageLink}
+ {consoleLink}
<li>
<a
href='#'
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx
index 1b722d611..a3f89a217 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/team_signup_url_page.jsx
@@ -35,8 +35,8 @@ export default class TeamSignupUrlPage extends React.Component {
if (cleanedName !== name || !urlRegex.test(name)) {
this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."});
return;
- } else if (cleanedName.length <= 3 || cleanedName.length > 15) {
- this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'});
+ } else if (cleanedName.length <= 2 || cleanedName.length > 15) {
+ this.setState({nameError: 'Name must be 3 or more characters up to a maximum of 15'});
return;
}
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
index a4972dd8d..14f281f7a 100644
--- a/web/react/components/team_signup_with_sso.jsx
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -23,12 +23,14 @@ export default class SSOSignUpPage extends React.Component {
team.display_name = this.state.name;
- if (team.display_name.length <= 3) {
+ if (!team.display_name) {
+ state.nameError = 'Please enter a team name';
+ this.setState(state);
return;
}
- if (!team.display_name) {
- state.nameError = 'Please enter a team name';
+ if (team.display_name.length <= 2) {
+ state.nameError = 'Name must be 3 or more characters up to a maximum of 15';
this.setState(state);
return;
}
@@ -68,7 +70,7 @@ export default class SSOSignUpPage extends React.Component {
}
var disabled = false;
- if (this.state.name.length <= 3) {
+ if (this.state.name.length <= 2) {
disabled = true;
}
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index ba14f019f..42c65ef5d 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -420,7 +420,7 @@ export default class NotificationsTab extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'Email notifications are sent for mentions and direct messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div>
+ <div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.config.SiteName + ' for more than 5 minutes.'}</div>
</div>
);
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 715e26197..b1be61fc7 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -332,6 +332,20 @@ export function saveConfig(config, success, error) {
});
}
+export function logClientError(msg) {
+ var l = {};
+ l.level = 'ERROR';
+ l.message = msg;
+
+ $.ajax({
+ url: '/api/v1/admin/log_client',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(l)
+ });
+}
+
export function testEmail(config, success, error) {
$.ajax({
url: '/api/v1/admin/test_email',
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index be11c2e40..8b20e2adf 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -78,6 +78,14 @@ export function isAdmin(roles) {
return false;
}
+export function isSystemAdmin(roles) {
+ if (isInRole(roles, 'system_admin')) {
+ return true;
+ }
+
+ return false;
+}
+
export function getDomainWithOutSub() {
var parts = window.location.host.split('.');
@@ -388,7 +396,6 @@ export function toTitleCase(str) {
export function applyTheme(theme) {
if (theme.sidebarBg) {
changeCss('.sidebar--left', 'background:' + theme.sidebarBg, 1);
- changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarBg, 1);
}
if (theme.sidebarText) {
@@ -423,6 +430,7 @@ export function applyTheme(theme) {
changeCss('.sidebar--left .team__header, .sidebar--menu .team__header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1);
+ changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarHeaderBg, 1);
}
if (theme.sidebarHeaderTextColor) {
@@ -430,8 +438,9 @@ export function applyTheme(theme) {
changeCss('.sidebar--left .team__header .user__name, .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8), 1);
changeCss('.sidebar--left .team__header:hover .user__name, .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('.modal .modal-header .modal-title, .modal .modal-header .modal-title .name, .modal .modal-header button.close', 'color:' + theme.sidebarHeaderTextColor, 1);
- changeCss('#navbar .navbar-default .navbar-brand .heading, ', 'color:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('#navbar .navbar-default .navbar-brand .heading', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('#navbar .navbar-default .navbar-toggle .icon-bar, ', 'background:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('@media(max-width: 768px){.search-bar__container', 'color:' + theme.sidebarHeaderTextColor, 2);
}
if (theme.onlineIndicator) {
@@ -452,7 +461,6 @@ export function applyTheme(theme) {
changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .command-box', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-create', 'background:' + theme.centerChannelBg, 1);
- changeCss('.search-bar__container .search__form .search-bar', 'background:' + theme.centerChannelBg, 1);
changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
changeCss('.post-image__column .post-image__details', 'background:' + theme.centerChannelBg, 1);
changeCss('.sidebar--right', 'background:' + theme.centerChannelBg, 1);
@@ -475,7 +483,7 @@ export function applyTheme(theme) {
changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2);
changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2);
changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1);
- changeCss('.search-bar__container .search__form .search-bar', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: ' + theme.centerChannelColor, 2);
+ changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
@@ -512,7 +520,6 @@ export function applyTheme(theme) {
changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2);
}
}
-
export function changeCss(className, classValue, classRepeat) {
// we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container');
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index d29c653ff..9e8d0dc7d 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -553,13 +553,7 @@
padding: 0 10px 0 31px;
background: rgba(black, 0.2);
@include border-radius(3px);
- color: #fff;
- }
- input[type=text] {
- @include input-placeholder {
- color: #fff;
- color: rgba(#fff, 0.5);
- }
+ color: inherit;
}
}
}
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index 9abdd40da..bcb8b5eac 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -32,8 +32,7 @@
top: 15px;
margin-left: 10px;
font-size: 14px;
- color: #fff;
- color: rgba(#fff, 0.5);
+ @include opacity(0.5);
display: none;
}
.search__form {
diff --git a/web/templates/head.html b/web/templates/head.html
index 2b83119d8..faac4975a 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -43,13 +43,32 @@
<script src="/static/js/jquery-dragster/jquery.dragster.js"></script>
<style id="antiClickjack">body{display:none !important;}</style>
+
+ <script>
+ window.onerror = function(msg, url, line, column, stack) {
+ var l = {};
+ l.level = 'ERROR';
+ l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url;
+
+ $.ajax({
+ url: '/api/v1/admin/log_client',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(l)
+ });
+ }
+ </script>
+
<script src="/static/js/bundle.js"></script>
+
<script type="text/javascript">
if (self === top) {
var blocker = document.getElementById("antiClickjack");
blocker.parentNode.removeChild(blocker);
}
</script>
+
<script type="text/javascript">
if (window.config.SegmentDeveloperKey != null && window.config.SegmentDeveloperKey !== "") {
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";