diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abbb64601..2ee47ec353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Mattermost Changelog -## UNDER DEVELOPMENT - Release v0.8.0 (Beta2) +## UNDER DEVELOPMENT - Release v1.0.0 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. @@ -15,11 +15,23 @@ The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the produ Messaging, Comments and Notifications -- (Preview) Added support for emoji codes rendering to image files +- Support for emoji codes rendering to image files +- Full markdown support in messages, comments, and channel description +- Added ability to play video and audio files -Admin Console +System Console -- (Preview) Ability to view server logs and change config settings +- UI to change config.json settings +- Ability to view log files from console + +User Interface + +- Ability to set custom theme colors +- Replaced single color themes with pre-set themes +- Added ability to import themes from Slack + +Integrations +- (Preview) Initial support for incoming webhooks ### Improvements @@ -30,6 +42,7 @@ Documentation - Added Code Contribution Guidelines - Added new hardware sizing recommendations - Consolidated licensing information into LICENSE.txt and NOTICE.txt +- Added markdown documentation Performance @@ -41,11 +54,11 @@ Code Quality UI -- Added version, build number, build date and build hash under Account Settings -> Security (to be moved to "About" dialog later) +- Added version, build number, build date and build hash under Account Settings -> Security ### Bug Fixes -- Fixed performance issue with slow typing on iOS +- Numerous performance improvements - Fixed issue so that SSO option automatically set EmailVerified=true (it was false previously) ### Contributors diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 671c37f91a..7c80558c4a 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -84,8 +84,8 @@ }, { "ImportPath": "github.com/mssola/user_agent", - "Comment": "v0.4.1-2-g35c7f18", - "Rev": "35c7f18f5261cc18c698a461053c119aebaf8542" + "Comment": "v0.4.1-4-ga163d6a", + "Rev": "a163d6a569f1cd264d2f8b2bf3c5d04ace5995eb" }, { "ImportPath": "github.com/rwcarlsen/goexif/exif", diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml b/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml index 9220912635..33c596acb4 100644 --- a/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml @@ -5,6 +5,7 @@ go: - 1.2 - 1.3 - 1.4 + - 1.5 - tip matrix: allow_failures: diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go b/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go index 6dca19d5ca..34ccbb8645 100644 --- a/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go @@ -40,6 +40,10 @@ var uastrings = []struct { {"IE11b32Win7b64MDDRJS", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MDDRJS; rv:11.0) like Gecko"}, {"IE11Compatibility", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0)"}, + // Microsoft Edge + {"EdgeDesktop", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"}, + {"EdgeMobile", "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; DEVICE INFO) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Edge/12.10240"}, + // Gecko {"FirefoxMac", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8"}, {"FirefoxMacLoc", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13"}, @@ -129,6 +133,10 @@ var expected = []string{ "Mozilla:5.0 Platform:Windows OS:Windows 7 Browser:Internet Explorer-11.0 Engine:Trident Bot:false Mobile:false", "Mozilla:4.0 Platform:Windows OS:Windows 8.1 Browser:Internet Explorer-7.0 Engine:Trident Bot:false Mobile:false", + // Microsoft Edge + "Mozilla:5.0 Platform:Windows OS:Windows NT 10.0 Browser:Edge-12.10240 Engine:EdgeHTML Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows Phone 10.0 Browser:Edge-12.10240 Engine:EdgeHTML Bot:false Mobile:true", + // Gecko "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10.6 Browser:Firefox-4.0b8 Engine:Gecko-20100101 Bot:false Mobile:false", "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10.6 Localization:en-US Browser:Firefox-3.6.13 Engine:Gecko-20101203 Bot:false Mobile:false", diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go b/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go index 9fb27e5f33..c5612db7b8 100644 --- a/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go @@ -48,13 +48,21 @@ func (p *UserAgent) detectBrowser(sections []section) { if slen > 2 { p.browser.Version = sections[2].version if engine.name == "AppleWebKit" { - if sections[slen-1].name == "OPR" { + switch sections[slen-1].name { + case "Edge": + p.browser.Name = "Edge" + p.browser.Version = sections[slen-1].version + p.browser.Engine = "EdgeHTML" + p.browser.EngineVersion = "" + case "OPR": p.browser.Name = "Opera" p.browser.Version = sections[slen-1].version - } else if sections[2].name == "Chrome" { - p.browser.Name = "Chrome" - } else { - p.browser.Name = "Safari" + default: + if sections[2].name == "Chrome" { + p.browser.Name = "Chrome" + } else { + p.browser.Name = "Safari" + } } } else if engine.name == "Gecko" { name := sections[2].name diff --git a/NOTICE.txt b/NOTICE.txt index f314724772..b7a7fbc1da 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -724,6 +724,77 @@ Legal Terms Our home page can be found at http://www.freetype.org - - + --- end of FTL.TXT --- + +--- + +This product contains a modified portion of 'gemoji', a collection of emoji images and names by Apple Inc. and other contributors. + +* HOMEPAGE: + * https://github.com/github/gemoji/blob/master/LICENSE + +* LICENSE: + +octocat, squirrel, shipit +Copyright (c) 2013 GitHub Inc. All rights reserved. + +bowtie, neckbeard, fu +Copyright (c) 2013 37signals, LLC. All rights reserved. + +feelsgood, finnadie, goberserk, godmode, hurtrealbad, rage 1-4, suspect +Copyright (c) 2013 id Software. All rights reserved. + +trollface +Copyright (c) 2013 whynne@deviantart. All rights reserved. + +All other images +Copyright (c) 2013 Apple Inc. All rights reserved. + +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 'marked', a full-featured markdown parser and compiler, written in JavaScript. Built for speed. + +by Christopher Jeffrey + +* HOMEPAGE: + * https://github.com/chjj/marked + +* LICENSE: + +Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/) + +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. diff --git a/api/context.go b/api/context.go index 9a276a1a18..d90fbd9ee3 100644 --- a/api/context.go +++ b/api/context.go @@ -456,18 +456,19 @@ func IsPrivateIpAddress(ipAddress string) bool { } func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) { + props := make(map[string]string) + props["Message"] = err.Message + props["Details"] = err.DetailedError - protocol := GetProtocol(r) - SiteURL := protocol + "://" + r.Host - - m := make(map[string]string) - m["Message"] = err.Message - m["Details"] = err.DetailedError - m["SiteName"] = utils.Cfg.TeamSettings.SiteName - m["SiteURL"] = SiteURL + pathParts := strings.Split(r.URL.Path, "/") + if len(pathParts) > 1 { + props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1] + } else { + props["SiteURL"] = GetProtocol(r) + "://" + r.Host + } w.WriteHeader(err.StatusCode) - ServerTemplates.ExecuteTemplate(w, "error.html", m) + ServerTemplates.ExecuteTemplate(w, "error.html", Page{Props: props, ClientProps: utils.ClientProperties}) } func Handle404(w http.ResponseWriter, r *http.Request) { diff --git a/api/export.go b/api/export.go index 73142a0e49..aff34073f0 100644 --- a/api/export.go +++ b/api/export.go @@ -87,7 +87,7 @@ func ExportTeams(writer ExportWriter, options *ExportOptions) *model.AppError { // Get the teams var teams []*model.Team if len(options.TeamsToExport) == 0 { - if result := <-Srv.Store.Team().GetForExport(); result.Err != nil { + if result := <-Srv.Store.Team().GetAll(); result.Err != nil { return result.Err } else { teams = result.Data.([]*model.Team) diff --git a/api/file.go b/api/file.go index 3606e406eb..be8fc5456d 100644 --- a/api/file.go +++ b/api/file.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" + "github.com/mssola/user_agent" "github.com/rwcarlsen/goexif/exif" _ "golang.org/x/image/bmp" "image" @@ -407,6 +408,19 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "max-age=2592000, public") w.Header().Set("Content-Length", strconv.Itoa(len(f))) w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(filename))) + + // attach extra headers to trigger a download on IE and Edge + ua := user_agent.New(r.UserAgent()) + bname, _ := ua.Browser() + + if bname == "Edge" || bname == "Internet Explorer" { + // trim off anything before the final / so we just get the file's name + parts := strings.Split(filename, "/") + + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment;filename=\""+parts[len(parts)-1]+"\"") + } + w.Write(f) } diff --git a/api/import.go b/api/import.go index e3f314d203..c465825b4e 100644 --- a/api/import.go +++ b/api/import.go @@ -6,7 +6,6 @@ package api import ( l4g "code.google.com/p/log4go" "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" ) // @@ -24,9 +23,6 @@ func ImportPost(post *model.Post) { func ImportUser(user *model.User) *model.User { user.MakeNonNil() - if len(user.Props["theme"]) == 0 { - user.AddProp("theme", utils.Cfg.TeamSettings.DefaultThemeColor) - } if result := <-Srv.Store.User().Save(user); result.Err != nil { l4g.Error("Error saving user. err=%v", result.Err) diff --git a/api/team.go b/api/team.go index c9d2412d37..4794b66df7 100644 --- a/api/team.go +++ b/api/team.go @@ -25,12 +25,12 @@ func InitTeam(r *mux.Router) { sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST") sr.Handle("/create_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).Methods("POST") sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST") + sr.Handle("/all", ApiUserRequired(getAll)).Methods("GET") sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST") sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST") sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST") sr.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST") sr.Handle("/update_name", ApiUserRequired(updateTeamDisplayName)).Methods("POST") - sr.Handle("/update_valet_feature", ApiUserRequired(updateValetFeature)).Methods("POST") sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET") // These should be moved to the global admain console sr.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST") @@ -302,6 +302,53 @@ func isTreamCreationAllowed(c *Context, email string) bool { return true } +func getAll(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.HasSystemAdminPermissions("getLogs") { + return + } + + if result := <-Srv.Store.Team().GetAll(); result.Err != nil { + c.Err = result.Err + return + } else { + teams := result.Data.([]*model.Team) + m := make(map[string]*model.Team) + for _, v := range teams { + m[v.Id] = v + } + + w.Write([]byte(model.TeamMapToJson(m))) + } +} + +func revokeAllSessions(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + id := props["id"] + + if result := <-Srv.Store.Session().Get(id); result.Err != nil { + c.Err = result.Err + return + } else { + session := result.Data.(*model.Session) + + c.LogAudit("revoked_all=" + id) + + if session.IsOAuth { + RevokeAccessToken(session.Token) + } else { + sessionCache.Remove(session.Token) + + if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil { + c.Err = result.Err + return + } else { + w.Write([]byte(model.MapToJson(props))) + return + } + } + } +} + func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { m := model.MapFromJson(r.Body) @@ -533,52 +580,6 @@ func updateTeamDisplayName(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(props))) } -func updateValetFeature(c *Context, w http.ResponseWriter, r *http.Request) { - - props := model.MapFromJson(r.Body) - - allowValetStr := props["allow_valet"] - if len(allowValetStr) == 0 { - c.SetInvalidParam("updateValetFeature", "allow_valet") - return - } - - teamId := props["team_id"] - if len(teamId) > 0 && len(teamId) != 26 { - c.SetInvalidParam("updateValetFeature", "team_id") - return - } else if len(teamId) == 0 { - teamId = c.Session.TeamId - } - - tchan := Srv.Store.Team().Get(teamId) - - if !c.HasPermissionsToTeam(teamId, "updateValetFeature") { - return - } - - if !model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) { - c.Err = model.NewAppError("updateValetFeature", "You do not have the appropriate permissions", "userId="+c.Session.UserId) - c.Err.StatusCode = http.StatusForbidden - return - } - - var team *model.Team - if tResult := <-tchan; tResult.Err != nil { - c.Err = tResult.Err - return - } else { - team = tResult.Data.(*model.Team) - } - - if result := <-Srv.Store.Team().Update(team); result.Err != nil { - c.Err = result.Err - return - } - - w.Write([]byte(model.MapToJson(props))) -} - func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) { if len(c.Session.TeamId) == 0 { diff --git a/api/team_test.go b/api/team_test.go index cd39dacfe5..e2a7cf4308 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -132,6 +132,39 @@ func TestFindTeamByEmail(t *testing.T) { } } +func TestGetAllTeams(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + if _, err := Client.GetAllTeams(); err == nil { + t.Fatal("you shouldn't have permissions") + } + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + if r1, err := Client.GetAllTeams(); err != nil { + t.Fatal(err) + } else { + teams := r1.Data.(map[string]*model.Team) + if teams[team.Id].Name != team.Name { + t.Fatal() + } + } +} + /* XXXXXX investigate and fix failing test diff --git a/api/templates/error.html b/api/templates/error.html index 7605788962..6b643556e0 100644 --- a/api/templates/error.html +++ b/api/templates/error.html @@ -23,7 +23,7 @@
{{.Message}}
+{{ .Props.Message }}
Go back to team site- Learn more by taking a tour -
- You joined the {{.Props.TeamDisplayName}} team at {{.ClientProps.SiteName}}!-Please let me know if you have any questions. |
-
|
- Any questions at all, mail us any time: {{.ClientProps.FeedbackEmail}}. - Best wishes, - The {{.ClientProps.SiteName}} Team + You can sign-in to your new team from the web address:+ {{.Props.TeamURL}} +Mattermost lets you share messages and files from your PC or phone, with instant search and archiving. |
{'Typically set to true in production. When true Mattermost will not allow a user to login without first having recieved an email with a verification link. Developers may set this field to false so skip sending verification emails for faster development.'}
+{'Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'}
@@ -296,12 +296,12 @@ export default class EmailSettings extends React.Component { className='form-control' id='feedbackName' ref='feedbackName' - placeholder='Ex: "Mattermost", "System", "John Smith"' + placeholder='Ex: "Mattermost Notification", "System", "No-Reply"' defaultValue={this.props.config.EmailSettings.FeedbackName} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} /> -{'Name displayed on email account used when sending notification emails from Mattermost.'}
+{'Display name on email account used when sending notification emails from Mattermost.'}
@@ -323,7 +323,7 @@ export default class EmailSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} /> -{'Email displayed on email account used when sending notification emails from Mattermost.'}
+{'Email address displayed on email account used when sending notification emails from Mattermost.'}
@@ -479,7 +479,7 @@ export default class EmailSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} /> -{'32-character salt added to signing of email invites.'}
+{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}
{'32-character salt added to signing of password reset emails.'}
+{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}
{'When true Mattermost will allow team creation and account signup utilizing GitLab OAuth.'}
+{'When true, Mattermost allows team creation and account signup using GitLab OAuth. To configure, log in to your GitLab account and go to Applications -> Profile Settings. Enter Redirect URIs "
{'Need help text.'}
+{'Obtain this value via the instructions above for logging into GitLab.'}
@@ -158,7 +158,7 @@ export default class GitLabSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.Allow} /> -{'Need help text.'}
+{'Obtain this value via the instructions above for logging into GitLab'}
@@ -175,12 +175,12 @@ export default class GitLabSettings extends React.Component { className='form-control' id='Scope' ref='Scope' - placeholder='Ex ""' + placeholder='Not currently used by GitLab. Please leave blank' defaultValue={this.props.config.GitLabSettings.Scope} onChange={this.handleChange} disabled={!this.state.Allow} /> -{'Need 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.'}
@@ -202,7 +202,7 @@ export default class GitLabSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.Allow} /> -{'Need help text.'}
+{'Enter
{'Need help text.'}
+{'Enter
{'Need help text.'}
+{'Enter
{'32-character salt added to signing of public image links.'}
+{'32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'}
{'This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers debugging issues working on debugging issues.'}
+{'This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'}
@@ -181,7 +181,7 @@ export default class LogSettings extends React.Component { /> {'false'} -{'Typically set to true in production. When true log files are written to the file specified in file location field below.'}
+{'Typically set to true in production. When true, log files are written to the log file specified in file location field below.'}
@@ -205,7 +205,7 @@ export default class LogSettings extends React.Component { -{'This setting determines the level of detail at which log events are written to the file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers debugging issues working on debugging issues.'}
+{'This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'}
@@ -227,7 +227,7 @@ export default class LogSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.fileEnable} /> -{'File to which log files are written. If blank, will be set to ./logs/mattermost.log. Log rotation is enabled and new files may be created in the same directory.'}
+{'File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to 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.'}
diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx index 8ce6939258..affd8ae116 100644 --- a/web/react/components/admin_console/privacy_settings.jsx +++ b/web/react/components/admin_console/privacy_settings.jsx @@ -99,7 +99,7 @@ export default class PrivacySettings extends React.Component { /> {'false'} -{'Hides email address of users from other users including team administrator.'}
+{'When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'}
@@ -132,7 +132,7 @@ export default class PrivacySettings extends React.Component { /> {'false'} -{'Hides full name of users from other users including team administrator.'}
+{'When false, hides full name of users from other users including team owner and team administrators.'}
diff --git a/web/react/components/admin_console/rate_settings.jsx b/web/react/components/admin_console/rate_settings.jsx index c05bf4a820..0081daca33 100644 --- a/web/react/components/admin_console/rate_settings.jsx +++ b/web/react/components/admin_console/rate_settings.jsx @@ -140,7 +140,7 @@ export default class RateSettings extends React.Component { /> {'false'} -{'When enabled throttles rate at which APIs respond.'}
+{'When true, APIs are throttled at rates specified below.'}
@@ -184,7 +184,7 @@ export default class RateSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.EnableRateLimiter} /> -{'Maximum number of users sessions connected to the system as determined by VaryByRemoteAddr and VaryByHeader variables.'}
+{'Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'}
@@ -193,7 +193,7 @@ export default class RateSettings extends React.Component { className='control-label col-sm-4' htmlFor='VaryByRemoteAddr' > - {'Limit By Remote Address: '} + {'Vary By Remote Address: '}{'Rate limit API access by IP address.'}
+{'When true, rate limit API access by IP address.'}
{'When filled in, 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").'}
+{'When filled in, 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").'}
{this.state.serverError}
{'The address to bind to and listen. ":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.'}
+{'The address to which to bind and listen. 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.'}
{'When enabled Mattermost will act as an Oauth2 Provider. Changing this will require a server restart before taking effect.'}
-{'When true incomming web hooks will be allowed.'}
+{'When true, incoming webhooks will be allowed.'}
{'When true slash commands like /loadtest are enabled in the add comment box. Changing this will require a server restart before taking effect. Typically used for development.'}
+{'(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.'}
@@ -291,6 +258,39 @@ export default class ServiceSettings extends React.Component { } } +//{'When enabled Mattermost will act as an OAuth2 Provider. Changing this will require a server restart before taking effect.'}
+//{'Output executing SQL statements to the log. Typically used for development.'}
+{'(Development Mode) When true, executing SQL statements are written to the log.'}
diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx index fefc0e936d..0f6f819d39 100644 --- a/web/react/components/admin_console/team_settings.jsx +++ b/web/react/components/admin_console/team_settings.jsx @@ -28,7 +28,6 @@ export default class TeamSettings extends React.Component { var config = this.props.config; config.TeamSettings.SiteName = React.findDOMNode(this.refs.SiteName).value.trim(); - config.TeamSettings.DefaultThemeColor = React.findDOMNode(this.refs.DefaultThemeColor).value.trim(); config.TeamSettings.RestrictCreationToDomains = React.findDOMNode(this.refs.RestrictCreationToDomains).value.trim(); config.TeamSettings.EnableTeamCreation = React.findDOMNode(this.refs.EnableTeamCreation).checked; config.TeamSettings.EnableUserCreation = React.findDOMNode(this.refs.EnableUserCreation).checked; @@ -118,28 +117,7 @@ export default class TeamSettings extends React.Component { defaultValue={this.props.config.TeamSettings.MaxUsersPerTeam} onChange={this.handleChange} /> -{'Maximum number of users per team.'}
- - - -{'Default theme color for team sites.'}
+{'Maximum total number of users per team, including both active and inactive users.'}
{'When false the ability to create teams is disabled. The create team button displays error when pressed.'}
+{'When false, the ability to create teams is disabled. The create team button displays error when pressed.'}
@@ -205,7 +183,7 @@ export default class TeamSettings extends React.Component { /> {'false'} -{'When false the ability to create accounts is disabled. The create account button displays error when pressed.'}
+{'When false, the ability to create accounts is disabled. The create account button displays error when pressed.'}
diff --git a/web/react/components/admin_console/team_users.jsx b/web/react/components/admin_console/team_users.jsx new file mode 100644 index 0000000000..0a971ff159 --- /dev/null +++ b/web/react/components/admin_console/team_users.jsx @@ -0,0 +1,178 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var LoadingScreen = require('../loading_screen.jsx'); +var UserItem = require('./user_item.jsx'); +var ResetPasswordModal = require('./reset_password_modal.jsx'); + +export default class UserList extends React.Component { + constructor(props) { + super(props); + + this.getTeamProfiles = this.getTeamProfiles.bind(this); + this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this); + this.doPasswordReset = this.doPasswordReset.bind(this); + this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this); + this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this); + + this.state = { + teamId: props.team.id, + users: null, + serverError: null, + showPasswordModal: false, + user: null + }; + } + + componentDidMount() { + this.getCurrentTeamProfiles(); + } + + getCurrentTeamProfiles() { + this.getTeamProfiles(this.props.team.id); + } + + // this.setState({ + // teamId: this.state.teamId, + // users: this.state.users, + // serverError: this.state.serverError, + // showPasswordModal: this.state.showPasswordModal, + // user: this.state.user + // }); + + getTeamProfiles(teamId) { + Client.getProfilesForTeam( + teamId, + (users) => { + var memberList = []; + for (var id in users) { + if (users.hasOwnProperty(id)) { + memberList.push(users[id]); + } + } + + memberList.sort((a, b) => { + if (a.username < b.username) { + return -1; + } + + if (a.username > b.username) { + return 1; + } + + return 0; + }); + + this.setState({ + teamId: this.state.teamId, + users: memberList, + serverError: this.state.serverError, + showPasswordModal: this.state.showPasswordModal, + user: this.state.user + }); + }, + (err) => { + this.setState({ + teamId: this.state.teamId, + users: null, + serverError: err.message, + showPasswordModal: this.state.showPasswordModal, + user: this.state.user + }); + } + ); + } + + doPasswordReset(user) { + this.setState({ + teamId: this.state.teamId, + users: this.state.users, + serverError: this.state.serverError, + showPasswordModal: true, + user + }); + } + + doPasswordResetDismiss() { + this.state.showPasswordModal = false; + this.state.user = null; + this.setState({ + teamId: this.state.teamId, + users: this.state.users, + serverError: this.state.serverError, + showPasswordModal: false, + user: null + }); + } + + doPasswordResetSubmit() { + this.setState({ + teamId: this.state.teamId, + users: this.state.users, + serverError: this.state.serverError, + showPasswordModal: false, + user: null + }); + } + + componentWillReceiveProps(newProps) { + this.getTeamProfiles(newProps.team.id); + } + + componentWillUnmount() { + } + + render() { + var serverError = ''; + if (this.state.serverError) { + serverError = ; + } + + if (this.state.users == null) { + return ( +{'Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}
-{'The Slack import to Mattermost is in "Preview". Slack bot posts do not yet import.'}
+{'The Slack import to Mattermost is in "Preview". Slack bot posts do not yet import and Slack @mentions are not currently supported.'}