summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoramWilander <jwawilander@gmail.com>2018-06-30 12:24:04 -0400
committerJoramWilander <jwawilander@gmail.com>2018-06-30 12:24:04 -0400
commit3848cb7e79e019e2f0878d6e2377ad36b3c7ca43 (patch)
tree98beeb962ffc6d7c3f8cd38651eaccab4a9685fa
parent88c5e469ca869d9e8ceadb0f2b03e86005102f24 (diff)
parent2362a5b7f7c2b4bf2b35b104e8616d847b33b0be (diff)
downloadchat-3848cb7e79e019e2f0878d6e2377ad36b3c7ca43.tar.gz
chat-3848cb7e79e019e2f0878d6e2377ad36b3c7ca43.tar.bz2
chat-3848cb7e79e019e2f0878d6e2377ad36b3c7ca43.zip
Merge branch 'master' into plugins-2
-rw-r--r--api4/user.go6
-rw-r--r--api4/user_test.go8
-rw-r--r--app/diagnostics.go15
-rw-r--r--app/import.go50
-rw-r--r--app/import_test.go120
-rw-r--r--app/server.go24
-rw-r--r--app/session.go3
-rw-r--r--app/status.go16
-rw-r--r--app/web_conn.go18
-rw-r--r--app/web_hub.go40
-rw-r--r--build/release.mk12
-rw-r--r--config/default.json1
-rw-r--r--i18n/en.json762
-rw-r--r--migrations/scheduler.go2
-rw-r--r--model/config.go27
-rw-r--r--store/sqlstore/channel_store.go2
-rw-r--r--store/sqlstore/supplier.go3
-rw-r--r--store/sqlstore/supplier_test.go32
-rw-r--r--store/sqlstore/user_access_token_store.go2
-rw-r--r--store/storetest/channel_store.go13
-rw-r--r--store/storetest/docker.go20
-rw-r--r--utils/subpath.go10
-rw-r--r--utils/subpath_test.go232
23 files changed, 683 insertions, 735 deletions
diff --git a/api4/user.go b/api4/user.go
index 14ab3a0a2..ac702644d 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -154,7 +154,11 @@ func getUserByUsername(c *Context, w http.ResponseWriter, r *http.Request) {
if c.HandleEtag(etag, "Get User", w, r) {
return
} else {
- c.App.SanitizeProfile(user, c.IsSystemAdmin())
+ if c.Session.UserId == user.Id {
+ user.Sanitize(map[string]bool{})
+ } else {
+ c.App.SanitizeProfile(user, c.IsSystemAdmin())
+ }
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(user.ToJson()))
return
diff --git a/api4/user_test.go b/api4/user_test.go
index 96aa55d5f..ad77c8c4c 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -411,7 +411,7 @@ func TestGetUserByUsername(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false })
th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = false })
- ruser, resp = Client.GetUserByUsername(user.Username, "")
+ ruser, resp = Client.GetUserByUsername(th.BasicUser2.Username, "")
CheckNoError(t, resp)
if ruser.Email != "" {
@@ -424,6 +424,12 @@ func TestGetUserByUsername(t *testing.T) {
t.Fatal("last name should be blank")
}
+ ruser, resp = Client.GetUserByUsername(th.BasicUser.Username, "")
+ CheckNoError(t, resp)
+ if len(ruser.NotifyProps) == 0 {
+ t.Fatal("notify props should be sent")
+ }
+
Client.Logout()
_, resp = Client.GetUserByUsername(user.Username, "")
CheckUnauthorizedStatus(t, resp)
diff --git a/app/diagnostics.go b/app/diagnostics.go
index 966cdb5be..dc0ab4ce8 100644
--- a/app/diagnostics.go
+++ b/app/diagnostics.go
@@ -297,13 +297,14 @@ func (a *App) trackConfig() {
})
a.SendDiagnostic(TRACK_CONFIG_SQL, map[string]interface{}{
- "driver_name": *cfg.SqlSettings.DriverName,
- "trace": cfg.SqlSettings.Trace,
- "max_idle_conns": *cfg.SqlSettings.MaxIdleConns,
- "max_open_conns": *cfg.SqlSettings.MaxOpenConns,
- "data_source_replicas": len(cfg.SqlSettings.DataSourceReplicas),
- "data_source_search_replicas": len(cfg.SqlSettings.DataSourceSearchReplicas),
- "query_timeout": *cfg.SqlSettings.QueryTimeout,
+ "driver_name": *cfg.SqlSettings.DriverName,
+ "trace": cfg.SqlSettings.Trace,
+ "max_idle_conns": *cfg.SqlSettings.MaxIdleConns,
+ "conn_max_lifetime_milliseconds": *cfg.SqlSettings.ConnMaxLifetimeMilliseconds,
+ "max_open_conns": *cfg.SqlSettings.MaxOpenConns,
+ "data_source_replicas": len(cfg.SqlSettings.DataSourceReplicas),
+ "data_source_search_replicas": len(cfg.SqlSettings.DataSourceSearchReplicas),
+ "query_timeout": *cfg.SqlSettings.QueryTimeout,
})
a.SendDiagnostic(TRACK_CONFIG_LOG, map[string]interface{}{
diff --git a/app/import.go b/app/import.go
index 64e53fe93..baf936567 100644
--- a/app/import.go
+++ b/app/import.go
@@ -1062,10 +1062,24 @@ func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *mod
}
var roles string
+ isSchemeUser := true
+ isSchemeAdmin := false
+
if tdata.Roles == nil {
- roles = model.TEAM_USER_ROLE_ID
+ isSchemeUser = true
} else {
- roles = *tdata.Roles
+ rawRoles := *tdata.Roles
+ explicitRoles := []string{}
+ for _, role := range strings.Fields(rawRoles) {
+ if role == model.TEAM_USER_ROLE_ID {
+ isSchemeUser = true
+ } else if role == model.TEAM_ADMIN_ROLE_ID {
+ isSchemeAdmin = true
+ } else {
+ explicitRoles = append(explicitRoles, role)
+ }
+ }
+ roles = strings.Join(explicitRoles, " ")
}
var member *model.TeamMember
@@ -1073,12 +1087,16 @@ func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *mod
return err
}
- if member.Roles != roles {
+ if member.ExplicitRoles != roles {
if _, err := a.UpdateTeamMemberRoles(team.Id, user.Id, roles); err != nil {
return err
}
}
+ if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser {
+ a.UpdateTeamMemberSchemeRoles(team.Id, user.Id, isSchemeUser, isSchemeAdmin)
+ }
+
if defaultChannel, err := a.GetChannelByName(model.DEFAULT_CHANNEL, team.Id); err != nil {
return err
} else if _, err = a.addUserToChannel(user, defaultChannel, member); err != nil {
@@ -1108,10 +1126,24 @@ func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember
}
var roles string
+ isSchemeUser := true
+ isSchemeAdmin := false
+
if cdata.Roles == nil {
- roles = model.CHANNEL_USER_ROLE_ID
+ isSchemeUser = true
} else {
- roles = *cdata.Roles
+ rawRoles := *cdata.Roles
+ explicitRoles := []string{}
+ for _, role := range strings.Fields(rawRoles) {
+ if role == model.CHANNEL_USER_ROLE_ID {
+ isSchemeUser = true
+ } else if role == model.CHANNEL_ADMIN_ROLE_ID {
+ isSchemeAdmin = true
+ } else {
+ explicitRoles = append(explicitRoles, role)
+ }
+ }
+ roles = strings.Join(explicitRoles, " ")
}
var member *model.ChannelMember
@@ -1123,12 +1155,16 @@ func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember
}
}
- if member.Roles != roles {
+ if member.ExplicitRoles != roles {
if _, err := a.UpdateChannelMemberRoles(channel.Id, user.Id, roles); err != nil {
return err
}
}
+ if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser {
+ a.UpdateChannelMemberSchemeRoles(channel.Id, user.Id, isSchemeUser, isSchemeAdmin)
+ }
+
if cdata.NotifyProps != nil {
notifyProps := member.NotifyProps
@@ -1200,7 +1236,7 @@ func validateUserImportData(data *UserImportData) *model.AppError {
}
if data.Password != nil && len(*data.Password) == 0 {
- return model.NewAppError("BulkImport", "app.import.validate_user_import_data.pasword_length.error", nil, "", http.StatusBadRequest)
+ return model.NewAppError("BulkImport", "app.import.validate_user_import_data.password_length.error", nil, "", http.StatusBadRequest)
}
if data.Password != nil && len(*data.Password) > model.USER_PASSWORD_MAX_LENGTH {
diff --git a/app/import_test.go b/app/import_test.go
index b27290289..e7bc055a4 100644
--- a/app/import_test.go
+++ b/app/import_test.go
@@ -2598,6 +2598,126 @@ func TestImportImportUser(t *testing.T) {
checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "false")
checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ANY)
checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "misc")
+
+ // Test importing a user with roles set to a team and a channel which are affected by an override scheme.
+ // The import subsystem should translate `channel_admin/channel_user/team_admin/team_user`
+ // to the appropriate scheme-managed-role booleans.
+
+ // Mark the phase 2 permissions migration as completed.
+ <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"})
+
+ defer func() {
+ <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2)
+ }()
+
+ teamSchemeData := &SchemeImportData{
+ Name: ptrStr(model.NewId()),
+ DisplayName: ptrStr(model.NewId()),
+ Scope: ptrStr("team"),
+ DefaultTeamUserRole: &RoleImportData{
+ Name: ptrStr(model.NewId()),
+ DisplayName: ptrStr(model.NewId()),
+ },
+ DefaultTeamAdminRole: &RoleImportData{
+ Name: ptrStr(model.NewId()),
+ DisplayName: ptrStr(model.NewId()),
+ },
+ DefaultChannelUserRole: &RoleImportData{
+ Name: ptrStr(model.NewId()),
+ DisplayName: ptrStr(model.NewId()),
+ },
+ DefaultChannelAdminRole: &RoleImportData{
+ Name: ptrStr(model.NewId()),
+ DisplayName: ptrStr(model.NewId()),
+ },
+ Description: ptrStr("description"),
+ }
+
+ if err := th.App.ImportScheme(teamSchemeData, false); err != nil {
+ t.Fatalf("Should have succeeded.")
+ }
+
+ var teamScheme *model.Scheme
+ if res := <-th.App.Srv.Store.Scheme().GetByName(*teamSchemeData.Name); res.Err != nil {
+ t.Fatalf("Failed to import scheme: %v", res.Err)
+ } else {
+ teamScheme = res.Data.(*model.Scheme)
+ }
+
+ teamData := &TeamImportData{
+ Name: ptrStr(model.NewId()),
+ DisplayName: ptrStr("Display Name"),
+ Type: ptrStr("O"),
+ Description: ptrStr("The team description."),
+ AllowOpenInvite: ptrBool(true),
+ Scheme: &teamScheme.Name,
+ }
+ if err := th.App.ImportTeam(teamData, false); err != nil {
+ t.Fatalf("Import should have succeeded: %v", err.Error())
+ }
+ team, err = th.App.GetTeamByName(teamName)
+ if err != nil {
+ t.Fatalf("Failed to get team from database.")
+ }
+
+ channelData := &ChannelImportData{
+ Team: &teamName,
+ Name: ptrStr(model.NewId()),
+ DisplayName: ptrStr("Display Name"),
+ Type: ptrStr("O"),
+ Header: ptrStr("Channe Header"),
+ Purpose: ptrStr("Channel Purpose"),
+ }
+ if err := th.App.ImportChannel(channelData, false); err != nil {
+ t.Fatalf("Import should have succeeded.")
+ }
+ channel, err = th.App.GetChannelByName(*channelData.Name, team.Id)
+ if err != nil {
+ t.Fatalf("Failed to get channel from database: %v", err.Error())
+ }
+
+ // Test with a valid team & valid channel name in apply mode.
+ userData := &UserImportData{
+ Username: &username,
+ Email: ptrStr(model.NewId() + "@example.com"),
+ Teams: &[]UserTeamImportData{
+ {
+ Name: &team.Name,
+ Roles: ptrStr("team_user team_admin"),
+ Channels: &[]UserChannelImportData{
+ {
+ Name: &channel.Name,
+ Roles: ptrStr("channel_admin channel_user"),
+ },
+ },
+ },
+ },
+ }
+ if err := th.App.ImportUser(userData, false); err != nil {
+ t.Fatalf("Should have succeeded.")
+ }
+
+ user, err = th.App.GetUserByUsername(*userData.Username)
+ if err != nil {
+ t.Fatalf("Failed to get user from database.")
+ }
+
+ teamMember, err := th.App.GetTeamMember(team.Id, user.Id)
+ if err != nil {
+ t.Fatalf("Failed to get the team member")
+ }
+ assert.True(t, teamMember.SchemeAdmin)
+ assert.True(t, teamMember.SchemeUser)
+ assert.Equal(t, "", teamMember.ExplicitRoles)
+
+ channelMember, err := th.App.GetChannelMember(channel.Id, user.Id)
+ if err != nil {
+ t.Fatalf("Failed to get the channel member")
+ }
+ assert.True(t, channelMember.SchemeAdmin)
+ assert.True(t, channelMember.SchemeUser)
+ assert.Equal(t, "", channelMember.ExplicitRoles)
+
}
func AssertAllPostsCount(t *testing.T, a *App, initialCount int64, change int64, teamName string) {
diff --git a/app/server.go b/app/server.go
index d71a884d2..769690295 100644
--- a/app/server.go
+++ b/app/server.go
@@ -92,15 +92,23 @@ func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
-func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) {
- if r.Host == "" {
- http.Error(w, "Not Found", http.StatusNotFound)
+// golang.org/x/crypto/acme/autocert/autocert.go
+func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" && r.Method != "HEAD" {
+ http.Error(w, "Use HTTPS", http.StatusBadRequest)
+ return
}
+ target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
+ http.Redirect(w, r, target, http.StatusFound)
+}
- url := r.URL
- url.Host = r.Host
- url.Scheme = "https"
- http.Redirect(w, r, url.String(), http.StatusFound)
+// golang.org/x/crypto/acme/autocert/autocert.go
+func stripPort(hostport string) string {
+ host, _, err := net.SplitHostPort(hostport)
+ if err != nil {
+ return hostport
+ }
+ return net.JoinHostPort(host, "443")
}
func (a *App) StartServer() error {
@@ -182,7 +190,7 @@ func (a *App) StartServer() error {
defer redirectListener.Close()
server := &http.Server{
- Handler: handler,
+ Handler: http.HandlerFunc(handleHTTPRedirect),
ErrorLog: a.Log.StdLog(mlog.String("source", "forwarder_server")),
}
server.Serve(redirectListener)
diff --git a/app/session.go b/app/session.go
index 53170cec0..5289aefaa 100644
--- a/app/session.go
+++ b/app/session.go
@@ -227,6 +227,9 @@ func (a *App) AttachDeviceId(sessionId string, deviceId string, expiresAt int64)
func (a *App) UpdateLastActivityAtIfNeeded(session model.Session) {
now := model.GetMillis()
+
+ a.UpdateWebConnUserActivity(session, now)
+
if now-session.LastActivityAt < model.SESSION_ACTIVITY_TIMEOUT {
return
}
diff --git a/app/status.go b/app/status.go
index e2367a396..460cbbbd0 100644
--- a/app/status.go
+++ b/app/status.go
@@ -161,6 +161,22 @@ func (a *App) GetUserStatusesByIds(userIds []string) ([]*model.Status, *model.Ap
return statusMap, nil
}
+// SetStatusLastActivityAt sets the last activity at for a user on the local app server and updates
+// status to away if needed. Used by the WS to set status to away if an 'online' device disconnects
+// while an 'away' device is still connected
+func (a *App) SetStatusLastActivityAt(userId string, activityAt int64) {
+ var status *model.Status
+ var err *model.AppError
+ if status, err = a.GetStatus(userId); err != nil {
+ return
+ }
+
+ status.LastActivityAt = activityAt
+
+ a.AddStatusCacheSkipClusterSend(status)
+ a.SetStatusAwayIfNeeded(userId, false)
+}
+
func (a *App) SetStatusOnline(userId string, sessionId string, manual bool) {
if !*a.Config().ServiceSettings.EnableUserStatuses {
return
diff --git a/app/web_conn.go b/app/web_conn.go
index 9d8134f34..dd01a8e31 100644
--- a/app/web_conn.go
+++ b/app/web_conn.go
@@ -33,6 +33,7 @@ type WebConn struct {
Send chan model.WebSocketMessage
sessionToken atomic.Value
session atomic.Value
+ LastUserActivityAt int64
UserId string
T goi18n.TranslateFunc
Locale string
@@ -52,14 +53,15 @@ func (a *App) NewWebConn(ws *websocket.Conn, session model.Session, t goi18n.Tra
}
wc := &WebConn{
- App: a,
- Send: make(chan model.WebSocketMessage, SEND_QUEUE_SIZE),
- WebSocket: ws,
- UserId: session.UserId,
- T: t,
- Locale: locale,
- endWritePump: make(chan struct{}, 2),
- pumpFinished: make(chan struct{}, 1),
+ App: a,
+ Send: make(chan model.WebSocketMessage, SEND_QUEUE_SIZE),
+ WebSocket: ws,
+ LastUserActivityAt: model.GetMillis(),
+ UserId: session.UserId,
+ T: t,
+ Locale: locale,
+ endWritePump: make(chan struct{}, 2),
+ pumpFinished: make(chan struct{}, 1),
}
wc.SetSession(&session)
diff --git a/app/web_hub.go b/app/web_hub.go
index 2ce78b5ef..5bb86ee38 100644
--- a/app/web_hub.go
+++ b/app/web_hub.go
@@ -23,6 +23,12 @@ const (
DEADLOCK_WARN = (BROADCAST_QUEUE_SIZE * 99) / 100 // number of buffered messages before printing stack trace
)
+type WebConnActivityMessage struct {
+ UserId string
+ SessionToken string
+ ActivityAt int64
+}
+
type Hub struct {
// connectionCount should be kept first.
// See https://github.com/mattermost/mattermost-server/pull/7281
@@ -35,6 +41,7 @@ type Hub struct {
stop chan struct{}
didStop chan struct{}
invalidateUser chan string
+ activity chan *WebConnActivityMessage
ExplicitStop bool
goroutineId int
}
@@ -48,6 +55,7 @@ func (a *App) NewWebHub() *Hub {
stop: make(chan struct{}),
didStop: make(chan struct{}),
invalidateUser: make(chan string),
+ activity: make(chan *WebConnActivityMessage),
ExplicitStop: false,
}
}
@@ -330,6 +338,13 @@ func (a *App) InvalidateWebConnSessionCacheForUser(userId string) {
}
}
+func (a *App) UpdateWebConnUserActivity(session model.Session, activityAt int64) {
+ hub := a.GetHubForUserId(session.UserId)
+ if hub != nil {
+ hub.UpdateActivity(session.UserId, session.Token, activityAt)
+ }
+}
+
func (h *Hub) Register(webConn *WebConn) {
h.register <- webConn
@@ -355,6 +370,10 @@ func (h *Hub) InvalidateUser(userId string) {
h.invalidateUser <- userId
}
+func (h *Hub) UpdateActivity(userId, sessionToken string, activityAt int64) {
+ h.activity <- &WebConnActivityMessage{UserId: userId, SessionToken: sessionToken, ActivityAt: activityAt}
+}
+
func getGoroutineId() int {
var buf [64]byte
n := runtime.Stack(buf[:], false)
@@ -395,15 +414,34 @@ func (h *Hub) Start() {
continue
}
- if len(connections.ForUser(webCon.UserId)) == 0 {
+ conns := connections.ForUser(webCon.UserId)
+ if len(conns) == 0 {
h.app.Go(func() {
h.app.SetStatusOffline(webCon.UserId, false)
})
+ } else {
+ var latestActivity int64 = 0
+ for _, conn := range conns {
+ if conn.LastUserActivityAt > latestActivity {
+ latestActivity = conn.LastUserActivityAt
+ }
+ }
+ if h.app.IsUserAway(latestActivity) {
+ h.app.Go(func() {
+ h.app.SetStatusLastActivityAt(webCon.UserId, latestActivity)
+ })
+ }
}
case userId := <-h.invalidateUser:
for _, webCon := range connections.ForUser(userId) {
webCon.InvalidateCache()
}
+ case activity := <-h.activity:
+ for _, webCon := range connections.ForUser(activity.UserId) {
+ if webCon.GetSessionToken() == activity.SessionToken {
+ webCon.LastUserActivityAt = activity.ActivityAt
+ }
+ }
case msg := <-h.broadcast:
candidates := connections.All()
if msg.Broadcast.UserId != "" {
diff --git a/build/release.mk b/build/release.mk
index 238343e82..9a4115600 100644
--- a/build/release.mk
+++ b/build/release.mk
@@ -3,27 +3,15 @@ dist: | check-style test package
build-linux:
@echo Build Linux amd64
-ifeq ($(BUILDER_GOOS_GOARCH),"linux_amd64")
- env GOOS=linux GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./...
-else
env GOOS=linux GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./...
-endif
build-osx:
@echo Build OSX amd64
-ifeq ($(BUILDER_GOOS_GOARCH),"darwin_amd64")
- env GOOS=darwin GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./...
-else
env GOOS=darwin GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./...
-endif
build-windows:
@echo Build Windows amd64
-ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64")
env GOOS=windows GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./...
-else
- env GOOS=windows GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./...
-endif
build: build-linux build-windows build-osx
diff --git a/config/default.json b/config/default.json
index 2d1e6ceec..6e4f6d1eb 100644
--- a/config/default.json
+++ b/config/default.json
@@ -119,6 +119,7 @@
"DataSourceReplicas": [],
"DataSourceSearchReplicas": [],
"MaxIdleConns": 20,
+ "ConnMaxLifetimeMilliseconds": 3600000,
"MaxOpenConns": 300,
"Trace": false,
"AtRestEncryptKey": "",
diff --git a/i18n/en.json b/i18n/en.json
index 7e08ad429..fd9888e39 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1,11 +1,11 @@
[
{
"id": "actiance.xml.output.formatter.marshalToXml.appError",
- "translation": ""
+ "translation": "Unable to convert export to XML."
},
{
"id": "api.admin.add_certificate.array.app_error",
- "translation": ""
+ "translation": "No file under 'certificate' in request."
},
{
"id": "api.admin.add_certificate.no_file.app_error",
@@ -37,7 +37,7 @@
},
{
"id": "api.admin.saml.not_available.app_error",
- "translation": ""
+ "translation": "SAML 2.0 is not configured or supported on this server."
},
{
"id": "api.admin.test_email.body",
@@ -189,7 +189,7 @@
},
{
"id": "api.channel.post_channel_privacy_message.error",
- "translation": ""
+ "translation": "Failed to post channel privacy update message."
},
{
"id": "api.channel.post_update_channel_displayname_message_and_forget.create_post.error",
@@ -253,7 +253,7 @@
},
{
"id": "api.channel.update_channel_scheme.license.error",
- "translation": "License does not support updating a channel's scheme"
+ "translation": "Your license does not support updating a channel's scheme"
},
{
"id": "api.channel.update_channel_scheme.scheme_scope.error",
@@ -962,7 +962,7 @@
},
{
"id": "api.emoji.create.other_user.app_error",
- "translation": ""
+ "translation": "Invalid user id"
},
{
"id": "api.emoji.create.parse.app_error",
@@ -1010,7 +1010,7 @@
},
{
"id": "api.emoji.upload.open.app_error",
- "translation": ""
+ "translation": "Unable to create the emoji. An error ocurred when trying to open the attached image."
},
{
"id": "api.file.attachments.disabled.app_error",
@@ -1266,7 +1266,7 @@
},
{
"id": "api.outgoing_webhook.disabled.app_error",
- "translation": ""
+ "translation": "Outgoing webhooks have been disabled by the system admin."
},
{
"id": "api.plugin.upload.array.app_error",
@@ -1400,15 +1400,15 @@
},
{
"id": "api.preference.delete_preferences.delete.app_error",
- "translation": ""
+ "translation": "Unable to delete user preferences."
},
{
"id": "api.preference.preferences_category.get.app_error",
- "translation": ""
+ "translation": "Unable to get user preferences."
},
{
"id": "api.preference.update_preferences.set.app_error",
- "translation": ""
+ "translation": "Unable to set user preferences."
},
{
"id": "api.reaction.save_reaction.invalid.app_error",
@@ -1420,15 +1420,15 @@
},
{
"id": "api.roles.patch_roles.license.error",
- "translation": "Your current license does not support advanced permissions."
+ "translation": "Your license does not support advanced permissions."
},
{
"id": "api.scheme.create_scheme.license.error",
- "translation": ""
+ "translation": "Your license does not support creating permissions schemes."
},
{
"id": "api.scheme.delete_scheme.license.error",
- "translation": ""
+ "translation": "Your license not support delete permissions schemes"
},
{
"id": "api.scheme.get_channels_for_scheme.scope.error",
@@ -1440,7 +1440,7 @@
},
{
"id": "api.scheme.patch_scheme.license.error",
- "translation": ""
+ "translation": "Your license does not support update permissions schemes"
},
{
"id": "api.server.start_server.forward80to443.disabled_while_using_lets_encrypt",
@@ -1560,11 +1560,11 @@
},
{
"id": "api.team.get_team_icon.filesettings_no_driver.app_error",
- "translation": ""
+ "translation": "Invalid driver name for file settings. Must be 'local' or 'amazons3'"
},
{
"id": "api.team.get_team_icon.read_file.app_error",
- "translation": ""
+ "translation": "Unable to read the team icon file."
},
{
"id": "api.team.import_team.array.app_error",
@@ -1696,7 +1696,7 @@
},
{
"id": "api.team.update_team_scheme.license.error",
- "translation": "License does not support updating a team's scheme"
+ "translation": "Your license does not support updating a team's scheme"
},
{
"id": "api.team.update_team_scheme.scheme_scope.error",
@@ -2036,7 +2036,7 @@
},
{
"id": "api.user.email_to_oauth.not_available.app_error",
- "translation": ""
+ "translation": "Authentication Transfer not configured or available on this server."
},
{
"id": "api.user.generate_mfa_qr.not_available.app_error",
@@ -2104,19 +2104,19 @@
},
{
"id": "api.user.oauth_to_email.not_available.app_error",
- "translation": ""
+ "translation": "Authentication Transfer not configured or available on this server."
},
{
"id": "api.user.reset_password.broken_token.app_error",
- "translation": ""
+ "translation": "The reset password token does not appear to be valid."
},
{
"id": "api.user.reset_password.invalid_link.app_error",
- "translation": "The reset password link does not appear to be valid"
+ "translation": "The reset password link does not appear to be valid."
},
{
"id": "api.user.reset_password.link_expired.app_error",
- "translation": "The password reset link has expired"
+ "translation": "The password reset link has expired."
},
{
"id": "api.user.reset_password.method",
@@ -2148,7 +2148,7 @@
},
{
"id": "api.user.send_mfa_change_email.error",
- "translation": ""
+ "translation": "Unable to send email notification for MFA change."
},
{
"id": "api.user.send_password_change_email_and_forget.error",
@@ -2264,7 +2264,7 @@
},
{
"id": "api.user.verify_email.broken_token.app_error",
- "translation": ""
+ "translation": "Bad verify email token type."
},
{
"id": "api.web_socket.connect.upgrade.app_error",
@@ -2272,19 +2272,19 @@
},
{
"id": "api.web_socket_router.bad_action.app_error",
- "translation": ""
+ "translation": "Unknown WebSocket action."
},
{
"id": "api.web_socket_router.bad_seq.app_error",
- "translation": ""
+ "translation": "Invalid sequence for WebSocket message."
},
{
"id": "api.web_socket_router.no_action.app_error",
- "translation": ""
+ "translation": "No websocket action."
},
{
"id": "api.web_socket_router.not_authenticated.app_error",
- "translation": ""
+ "translation": "WebSocket connection is not authenticated. Please log in and try again."
},
{
"id": "api.webhook.create_outgoing.intersect.app_error",
@@ -2360,7 +2360,7 @@
},
{
"id": "app.cluster.404.app_error",
- "translation": ""
+ "translation": "Cluster API endpoint not found."
},
{
"id": "app.import.bulk_import.file_scan.error",
@@ -2372,7 +2372,7 @@
},
{
"id": "app.import.bulk_import.unsupported_version.error",
- "translation": ""
+ "translation": "Incorrect or missing version in the data import file. Make sure version is the first object in your import file and try again."
},
{
"id": "app.import.import_channel.scheme_deleted.error",
@@ -2484,7 +2484,7 @@
},
{
"id": "app.import.import_user.save_preferences.error",
- "translation": ""
+ "translation": "Error importing user preferences. Failed to save preferences."
},
{
"id": "app.import.import_user_channels.save_preferences.error",
@@ -2492,7 +2492,7 @@
},
{
"id": "app.import.process_import_data_file_version_line.invalid_version.error",
- "translation": ""
+ "translation": "Unable to read the version of the data import file."
},
{
"id": "app.import.validate_channel_import_data.display_name_length.error",
@@ -2808,7 +2808,7 @@
},
{
"id": "app.import.validate_user_import_data.notify_props_comments_trigger_invalid.error",
- "translation": ""
+ "translation": "Invalid Comments Prop value for user."
},
{
"id": "app.import.validate_user_import_data.notify_props_desktop_invalid.error",
@@ -2832,10 +2832,6 @@
},
{
"id": "app.import.validate_user_import_data.password_length.error",
- "translation": ""
- },
- {
- "id": "app.import.validate_user_import_data.pasword_length.error",
"translation": "User Password has invalid length."
},
{
@@ -2960,7 +2956,7 @@
},
{
"id": "app.plugin.get_cluster_plugin_statuses.app_error",
- "translation": ""
+ "translation": "Unable to get plugin statuses from the cluster."
},
{
"id": "app.plugin.get_plugins.app_error",
@@ -3020,7 +3016,7 @@
},
{
"id": "app.user.complete_switch_with_oauth.blank_email.app_error",
- "translation": ""
+ "translation": "Unable to complete SAML login with an empty email address."
},
{
"id": "app.user_access_token.disabled",
@@ -3031,548 +3027,28 @@
"translation": "Invalid or missing token"
},
{
- "id": "authentication.permissions.add_reaction.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.add_reaction.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.add_user_to_team.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.add_user_to_team.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.assign_system_admin_role.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.assign_system_admin_role.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_direct_channel.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_direct_channel.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_group_channel.description",
- "translation": "Ability to create new group message channels"
- },
- {
- "id": "authentication.permissions.create_group_channel.name",
- "translation": "Create Group Message"
- },
- {
- "id": "authentication.permissions.create_post.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_post.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_post_ephemeral.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_post_ephemeral.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_post_public.description",
- "translation": "Ability to create posts in public channels"
- },
- {
- "id": "authentication.permissions.create_post_public.name",
- "translation": "Create Posts in Public Channels"
- },
- {
- "id": "authentication.permissions.create_private_channel.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_private_channel.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_public_channel.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_public_channel.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_team.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_team.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.create_user_access_token.description",
- "translation": "Ability to create personal access tokens"
- },
- {
- "id": "authentication.permissions.create_user_access_token.name",
- "translation": "Create Personal Access Token"
- },
- {
- "id": "authentication.permissions.delete_others_posts.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.delete_others_posts.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.delete_post.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.delete_post.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.delete_private_channel.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.delete_private_channel.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.delete_public_channel.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.delete_public_channel.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.edit_other_users.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.edit_other_users.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.edit_others_posts.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.edit_others_posts.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.edit_post.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.edit_post.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.get_public_link.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.get_public_link.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.import_team.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.import_team.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.join_public_channels.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.join_public_channels.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.list_team_channels.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.list_team_channels.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.list_users_without_team.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.list_users_without_team.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_channel_roles.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_channel_roles.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_emojis.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_emojis.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_oauth.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_oauth.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_others_emojis.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_others_emojis.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_others_slash_commands.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_others_slash_commands.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_others_webhooks.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_others_webhooks.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_private_channel_members.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_private_channel_members.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_private_channel_properties.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_private_channel_properties.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_public_channel_members.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_public_channel_members.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_public_channel_properties.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_public_channel_properties.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_roles.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_roles.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_slash_commands.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_slash_commands.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_system.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_system.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_system_wide_oauth.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_system_wide_oauth.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_team.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_team.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_team_roles.description",
- "translation": "Ability to change the roles of a team member"
- },
- {
- "id": "authentication.permissions.manage_team_roles.name",
- "translation": "Manage Team Roles"
- },
- {
- "id": "authentication.permissions.manage_webhooks.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.manage_webhooks.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.permanent_delete_user.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.permanent_delete_user.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.read_channel.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.read_channel.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.read_public_channel.description",
- "translation": "Ability to read public channels"
- },
- {
- "id": "authentication.permissions.read_public_channel.name",
- "translation": "Read Public Channels"
- },
- {
- "id": "authentication.permissions.read_user_access_token.description",
- "translation": "Ability to read personal access tokens' id, description and user_id fields"
- },
- {
- "id": "authentication.permissions.read_user_access_token.name",
- "translation": "Read Personal Access Tokens"
- },
- {
- "id": "authentication.permissions.remove_others_reactions.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.remove_others_reactions.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.remove_reaction.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.remove_reaction.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.remove_user_from_team.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.remove_user_from_team.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.revoke_user_access_token.description",
- "translation": "Ability to revoke personal access tokens"
- },
- {
- "id": "authentication.permissions.revoke_user_access_token.name",
- "translation": "Revoke Personal Access Token"
- },
- {
- "id": "authentication.permissions.team_invite_user.description",
- "translation": "Ability to invite users to a team"
- },
- {
- "id": "authentication.permissions.team_invite_user.name",
- "translation": "Invite User"
- },
- {
- "id": "authentication.permissions.team_use_slash_commands.description",
- "translation": "Ability to use slash commands"
- },
- {
- "id": "authentication.permissions.team_use_slash_commands.name",
- "translation": "Use Slash Commands"
- },
- {
- "id": "authentication.permissions.upload_file.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.upload_file.name",
- "translation": ""
- },
- {
- "id": "authentication.permissions.view_team.description",
- "translation": ""
- },
- {
- "id": "authentication.permissions.view_team.name",
- "translation": ""
- },
- {
- "id": "authentication.permisssions.manage_jobs.description",
- "translation": ""
- },
- {
- "id": "authentication.permisssions.manage_jobs.name",
- "translation": ""
- },
- {
- "id": "authentication.roles.channel_admin.description",
- "translation": ""
- },
- {
- "id": "authentication.roles.channel_admin.name",
- "translation": ""
- },
- {
- "id": "authentication.roles.channel_user.description",
- "translation": ""
- },
- {
- "id": "authentication.roles.channel_user.name",
- "translation": ""
- },
- {
- "id": "authentication.roles.global_admin.description",
- "translation": ""
- },
- {
- "id": "authentication.roles.global_admin.name",
- "translation": ""
- },
- {
- "id": "authentication.roles.global_user.description",
- "translation": ""
- },
- {
- "id": "authentication.roles.global_user.name",
- "translation": ""
- },
- {
- "id": "authentication.roles.system_post_all.description",
- "translation": "A role with the permission to post in any public, private or direct channel on the system"
- },
- {
- "id": "authentication.roles.system_post_all.name",
- "translation": "Post in Public, Private and Direct Channels"
- },
- {
- "id": "authentication.roles.system_post_all_public.description",
- "translation": "A role with the permission to post in any public channel on the system"
- },
- {
- "id": "authentication.roles.system_post_all_public.name",
- "translation": "Post in Public Channels"
- },
- {
- "id": "authentication.roles.system_user_access_token.description",
- "translation": "A role with the permissions to create, read and revoke personal access tokens"
- },
- {
- "id": "authentication.roles.system_user_access_token.name",
- "translation": "Personal Access Token"
- },
- {
- "id": "authentication.roles.team_admin.description",
- "translation": ""
- },
- {
- "id": "authentication.roles.team_admin.name",
- "translation": ""
- },
- {
- "id": "authentication.roles.team_post_all.description",
- "translation": "A role with the permission to post in any public or private channel on the team"
- },
- {
- "id": "authentication.roles.team_post_all.name",
- "translation": "Post in Public and Private Channels"
- },
- {
- "id": "authentication.roles.team_post_all_public.description",
- "translation": "A role with the permission to post in any public channel on the team"
- },
- {
- "id": "authentication.roles.team_post_all_public.name",
- "translation": "Post in Public Channels"
- },
- {
- "id": "authentication.roles.team_user.description",
- "translation": ""
- },
- {
- "id": "authentication.roles.team_user.name",
- "translation": ""
- },
- {
"id": "brand.save_brand_image.decode.app_error",
- "translation": ""
+ "translation": "Unable to decode the image data."
},
{
"id": "brand.save_brand_image.decode_config.app_error",
- "translation": ""
+ "translation": "Unable to get image metadata."
},
{
"id": "brand.save_brand_image.encode.app_error",
- "translation": ""
+ "translation": "Unable to convert the image data to PNG format. Please try again."
},
{
"id": "brand.save_brand_image.open.app_error",
- "translation": ""
+ "translation": "Unable to upload the custom brand image. Make sure the image size is less than 2 MB and try again."
},
{
"id": "brand.save_brand_image.save_image.app_error",
- "translation": ""
+ "translation": "Unable to write the image file to your file storage. Please check your connection and try again."
},
{
"id": "brand.save_brand_image.too_large.app_error",
- "translation": ""
+ "translation": "Unable to read the image file. Make sure the image size is less than 2 MB and try again."
},
{
"id": "cli.license.critical",
@@ -3580,19 +3056,19 @@
},
{
"id": "ent.account_migration.get_all_failed",
- "translation": ""
+ "translation": "Unable to get users."
},
{
"id": "ent.account_migration.get_saml_users_failed",
- "translation": ""
+ "translation": "Unable to get SAML users."
},
{
"id": "ent.cluster.config_changed.info",
- "translation": "Cluster configuration has changed for id={{ .id }}. The cluster may become unstable and a restart is required. To ensure the cluster is configured correctly you should perform a rolling restart immediately."
+ "translation": "Cluster configuration has changed for id={{ .id }}. The cluster may become unstable and a restart is required. To ensure the cluster is configured correctly you should perform a rolling restart immediately."
},
{
"id": "ent.cluster.save_config.error",
- "translation": "System Console is set to read-only when High Availability is enabled unless ReadOnlyConfig is disabled in the configuration file."
+ "translation": "System Console is set to read-only when High Availability is enabled unless ReadOnlyConfig is disabled in the configuration file."
},
{
"id": "ent.compliance.bad_export_type.appError",
@@ -3620,19 +3096,19 @@
},
{
"id": "ent.compliance.csv.metadata.json.marshalling.appError",
- "translation": ""
+ "translation": "Unable to convert metadata to json."
},
{
"id": "ent.compliance.csv.post.export.appError",
- "translation": ""
+ "translation": "Unable to export a post."
},
{
"id": "ent.compliance.csv.zip.creation.appError",
- "translation": ""
+ "translation": "Unable to create the zip export file."
},
{
"id": "ent.compliance.global_relay.attachments_removed.appError",
- "translation": ""
+ "translation": "Uploaded file was removed from Global Relay export because it was too large to send."
},
{
"id": "ent.compliance.licence_disable.app_error",
@@ -3640,7 +3116,7 @@
},
{
"id": "ent.compliance.run_export.template_watcher.appError",
- "translation": ""
+ "translation": "Unable to load export templates. Please try again."
},
{
"id": "ent.compliance.run_failed.error",
@@ -3648,7 +3124,7 @@
},
{
"id": "ent.data_retention.generic.license.error",
- "translation": "License does not support Data Retention."
+ "translation": "Your license does not support Data Retention."
},
{
"id": "ent.elasticsearch.aggregator_worker.create_index_job.error",
@@ -3748,7 +3224,7 @@
},
{
"id": "ent.elasticsearch.test_config.license.error",
- "translation": "License does not support Elasticsearch."
+ "translation": "Your license does not support Elasticsearch."
},
{
"id": "ent.elasticsearch.test_config.reenter_password",
@@ -3800,7 +3276,7 @@
},
{
"id": "ent.ldap.syncronize.search_failure.app_error",
- "translation": ""
+ "translation": "Failed to search users in AD/LDAP. Test if the Mattermost server can connect to your AD/LDAP server and try again."
},
{
"id": "ent.ldap.validate_filter.app_error",
@@ -3924,11 +3400,11 @@
},
{
"id": "jobs.do_job.batch_size.parse_error",
- "translation": ""
+ "translation": "Could not parse message export job BatchSize."
},
{
"id": "jobs.do_job.batch_start_timestamp.parse_error",
- "translation": ""
+ "translation": "Could not parse message export job ExportFromTimestamp."
},
{
"id": "jobs.request_cancellation.status.error",
@@ -3940,7 +3416,7 @@
},
{
"id": "jobs.start_synchronize_job.timeout",
- "translation": ""
+ "translation": "Reached AD/LDAP sychronization job timeout."
},
{
"id": "manaultesting.manual_test.parse.app_error",
@@ -4104,7 +3580,7 @@
},
{
"id": "model.client.get_team_icon.app_error",
- "translation": ""
+ "translation": "Unable to read the team icon from the body response."
},
{
"id": "model.client.read_file.app_error",
@@ -4120,11 +3596,11 @@
},
{
"id": "model.client.set_team_icon.no_file.app_error",
- "translation": ""
+ "translation": "No file under 'image' in request."
},
{
"id": "model.client.set_team_icon.writer.app_error",
- "translation": ""
+ "translation": "Unable to write the request."
},
{
"id": "model.client.upload_post_attachment.channel_id.app_error",
@@ -4336,11 +3812,11 @@
},
{
"id": "model.config.is_valid.email_batching_buffer_size.app_error",
- "translation": "Invalid email batching buffer size for email settings. Must be zero or a positive number."
+ "translation": "Invalid email batching buffer size for email settings. Must be zero or a positive number."
},
{
"id": "model.config.is_valid.email_batching_interval.app_error",
- "translation": "Invalid email batching interval for email settings. Must be 30 seconds or more."
+ "translation": "Invalid email batching interval for email settings. Must be 30 seconds or more."
},
{
"id": "model.config.is_valid.email_notification_contents_type.app_error",
@@ -4348,23 +3824,23 @@
},
{
"id": "model.config.is_valid.email_salt.app_error",
- "translation": "Invalid invite salt for email settings. Must be 32 chars or more."
+ "translation": "Invalid invite salt for email settings. Must be 32 chars or more."
},
{
"id": "model.config.is_valid.email_security.app_error",
- "translation": "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'"
+ "translation": "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'"
},
{
"id": "model.config.is_valid.encrypt_sql.app_error",
- "translation": "Invalid at rest encrypt key for SQL settings. Must be 32 chars or more."
+ "translation": "Invalid at rest encrypt key for SQL settings. Must be 32 chars or more."
},
{
"id": "model.config.is_valid.file_driver.app_error",
- "translation": "Invalid driver name for file settings. Must be 'local' or 'amazons3'"
+ "translation": "Invalid driver name for file settings. Must be 'local' or 'amazons3'"
},
{
"id": "model.config.is_valid.file_salt.app_error",
- "translation": "Invalid public link salt for file settings. Must be 32 chars or more."
+ "translation": "Invalid public link salt for file settings. Must be 32 chars or more."
},
{
"id": "model.config.is_valid.group_unread_channels.app_error",
@@ -4396,7 +3872,7 @@
},
{
"id": "model.config.is_valid.ldap_security.app_error",
- "translation": "Invalid connection security for AD/LDAP settings. Must be '', 'TLS', or 'STARTTLS'"
+ "translation": "Invalid connection security for AD/LDAP settings. Must be '', 'TLS', or 'STARTTLS'"
},
{
"id": "model.config.is_valid.ldap_server",
@@ -4420,7 +3896,7 @@
},
{
"id": "model.config.is_valid.login_attempts.app_error",
- "translation": "Invalid maximum login attempts for service settings. Must be a positive number."
+ "translation": "Invalid maximum login attempts for service settings. Must be a positive number."
},
{
"id": "model.config.is_valid.max_burst.app_error",
@@ -4428,7 +3904,7 @@
},
{
"id": "model.config.is_valid.max_channels.app_error",
- "translation": "Invalid maximum channels per team for team settings. Must be a positive number."
+ "translation": "Invalid maximum channels per team for team settings. Must be a positive number."
},
{
"id": "model.config.is_valid.max_file_size.app_error",
@@ -4436,11 +3912,11 @@
},
{
"id": "model.config.is_valid.max_notify_per_channel.app_error",
- "translation": "Invalid maximum notifications per channel for team settings. Must be a positive number."
+ "translation": "Invalid maximum notifications per channel for team settings. Must be a positive number."
},
{
"id": "model.config.is_valid.max_users.app_error",
- "translation": "Invalid maximum users per team for team settings. Must be a positive number."
+ "translation": "Invalid maximum users per team for team settings. Must be a positive number."
},
{
"id": "model.config.is_valid.message_export.batch_size.app_error",
@@ -4488,11 +3964,11 @@
},
{
"id": "model.config.is_valid.rate_mem.app_error",
- "translation": "Invalid memory store size for rate limit settings. Must be a positive number"
+ "translation": "Invalid memory store size for rate limit settings. Must be a positive number"
},
{
"id": "model.config.is_valid.rate_sec.app_error",
- "translation": "Invalid per sec for rate limit settings. Must be a positive number"
+ "translation": "Invalid per sec for rate limit settings. Must be a positive number"
},
{
"id": "model.config.is_valid.read_timeout.app_error",
@@ -4500,7 +3976,7 @@
},
{
"id": "model.config.is_valid.restrict_direct_message.app_error",
- "translation": "Invalid direct message restriction. Must be 'any', or 'team'"
+ "translation": "Invalid direct message restriction. Must be 'any', or 'team'"
},
{
"id": "model.config.is_valid.saml_assertion_consumer_service_url.app_error",
@@ -4548,27 +4024,31 @@
},
{
"id": "model.config.is_valid.sql_data_src.app_error",
- "translation": "Invalid data source for SQL settings. Must be set."
+ "translation": "Invalid data source for SQL settings. Must be set."
},
{
"id": "model.config.is_valid.sql_driver.app_error",
- "translation": "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'"
+ "translation": "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'"
},
{
"id": "model.config.is_valid.sql_idle.app_error",
- "translation": "Invalid maximum idle connection for SQL settings. Must be a positive number."
+ "translation": "Invalid maximum idle connection for SQL settings. Must be a positive number."
},
{
"id": "model.config.is_valid.sql_max_conn.app_error",
- "translation": "Invalid maximum open connection for SQL settings. Must be a positive number."
+ "translation": "Invalid maximum open connection for SQL settings. Must be a positive number."
+ },
+ {
+ "id": "model.config.is_valid.sql_conn_max_lifetime_milliseconds.app_error",
+ "translation": "Invalid connection maximum lifetime for SQL settings. Must be a non-negative number."
},
{
"id": "model.config.is_valid.sql_query_timeout.app_error",
- "translation": "Invalid query timeout for SQL settings. Must be a positive number."
+ "translation": "Invalid query timeout for SQL settings. Must be a positive number."
},
{
"id": "model.config.is_valid.teammate_name_display.app_error",
- "translation": "Invalid teammate display. Must be 'full_name', 'nickname_full_name' or 'username'"
+ "translation": "Invalid teammate display. Must be 'full_name', 'nickname_full_name' or 'username'"
},
{
"id": "model.config.is_valid.time_between_user_typing.app_error",
@@ -4632,7 +4112,7 @@
},
{
"id": "model.emoji.user_id.app_error",
- "translation": ""
+ "translation": "Invalid creator id"
},
{
"id": "model.file_info.get.gif.app_error",
@@ -4640,27 +4120,27 @@
},
{
"id": "model.file_info.is_valid.create_at.app_error",
- "translation": ""
+ "translation": "Invalid value for create_at."
},
{
"id": "model.file_info.is_valid.id.app_error",
- "translation": ""
+ "translation": "Invalid value for id."
},
{
"id": "model.file_info.is_valid.path.app_error",
- "translation": ""
+ "translation": "Invalid value for path."
},
{
"id": "model.file_info.is_valid.post_id.app_error",
- "translation": ""
+ "translation": "Invalid value for post_id."
},
{
"id": "model.file_info.is_valid.update_at.app_error",
- "translation": ""
+ "translation": "Invalid value for update_at."
},
{
"id": "model.file_info.is_valid.user_id.app_error",
- "translation": ""
+ "translation": "Invalid value for user_id."
},
{
"id": "model.incoming_hook.channel_id.app_error",
@@ -4724,11 +4204,11 @@
},
{
"id": "model.license_record.is_valid.create_at.app_error",
- "translation": ""
+ "translation": "Invalid value for create_at when uploading a license."
},
{
"id": "model.license_record.is_valid.id.app_error",
- "translation": ""
+ "translation": "Invalid value for id when uploading a license."
},
{
"id": "model.oauth.is_valid.app_id.app_error",
@@ -4780,7 +4260,7 @@
},
{
"id": "model.outgoing_hook.is_valid.content_type.app_error",
- "translation": ""
+ "translation": "Invalid value for content_type"
},
{
"id": "model.outgoing_hook.is_valid.create_at.app_error",
@@ -5008,7 +4488,7 @@
},
{
"id": "model.user_access_token.is_valid.id.app_error",
- "translation": ""
+ "translation": "Invalid value for id"
},
{
"id": "model.user_access_token.is_valid.token.app_error",
@@ -5024,7 +4504,7 @@
},
{
"id": "model.websocket_client.connect_fail.app_error",
- "translation": ""
+ "translation": "Unable to connect to the WebSocket server."
},
{
"id": "oauth.gitlab.tos.error",
@@ -5144,11 +4624,11 @@
},
{
"id": "store.sql_channel.get_deleted.existing.app_error",
- "translation": ""
+ "translation": "We couldn't find the existing deleted channel"
},
{
"id": "store.sql_channel.get_deleted.missing.app_error",
- "translation": ""
+ "translation": "No deleted channels exist"
},
{
"id": "store.sql_channel.get_deleted_by_name.existing.app_error",
@@ -5672,7 +5152,7 @@
},
{
"id": "store.sql_post.compliance_export.app_error",
- "translation": ""
+ "translation": "We couldn't get the compliance export posts."
},
{
"id": "store.sql_post.delete.app_error",
@@ -5684,7 +5164,7 @@
},
{
"id": "store.sql_post.get_flagged_posts.app_error",
- "translation": ""
+ "translation": "We couldn't get the flagged posts"
},
{
"id": "store.sql_post.get_parents_posts.app_error",
@@ -5832,7 +5312,7 @@
},
{
"id": "store.sql_reaction.delete.app_error",
- "translation": ""
+ "translation": "Unable to delete reaction"
},
{
"id": "store.sql_reaction.delete.begin.app_error",
@@ -5844,11 +5324,11 @@
},
{
"id": "store.sql_reaction.delete_all_with_emoji_name.delete_reactions.app_error",
- "translation": ""
+ "translation": "Unable to delete all reactions with this emoji name"
},
{
"id": "store.sql_reaction.delete_all_with_emoji_name.get_reactions.app_error",
- "translation": ""
+ "translation": "Unable to get all reactions with this emoji name"
},
{
"id": "store.sql_reaction.get_for_post.app_error",
@@ -5872,15 +5352,15 @@
},
{
"id": "store.sql_recover.delete.app_error",
- "translation": ""
+ "translation": "Unable to delete token"
},
{
"id": "store.sql_recover.get_by_code.app_error",
- "translation": ""
+ "translation": "Unable to get a token with this code"
},
{
"id": "store.sql_recover.save.app_error",
- "translation": ""
+ "translation": "Unable to save the token"
},
{
"id": "store.sql_role.delete.update.app_error",
@@ -6052,7 +5532,7 @@
},
{
"id": "store.sql_status.update_last_activity_at.app_error",
- "translation": ""
+ "translation": "Unable to update the last activity date and time of the user"
},
{
"id": "store.sql_system.get.app_error",
@@ -6236,11 +5716,11 @@
},
{
"id": "store.sql_team.update_last_team_icon_update.app_error",
- "translation": ""
+ "translation": "We couldn't update the date of the last team icon update"
},
{
"id": "store.sql_user.analytics_daily_active_users.app_error",
- "translation": ""
+ "translation": "We couldn't get the active users during the requested period"
},
{
"id": "store.sql_user.analytics_get_inactive_users_count.app_error",
@@ -6376,7 +5856,7 @@
},
{
"id": "store.sql_user.search.app_error",
- "translation": ""
+ "translation": "We couldn't find any user maching the search parameters"
},
{
"id": "store.sql_user.update.app_error",
@@ -6436,7 +5916,7 @@
},
{
"id": "store.sql_user.update_update.app_error",
- "translation": ""
+ "translation": "We couldn't update the date of the last update of the user"
},
{
"id": "store.sql_user.verify_email.app_error",
@@ -6472,15 +5952,11 @@
},
{
"id": "store.sql_user_access_token.update_token_disable.app_error",
- "translation": ""
- },
- {
- "id": "store.sql_user_access_token.update_token_disble.app_error",
- "translation": ""
+ "translation": "We couldn't disable the access token"
},
{
"id": "store.sql_user_access_token.update_token_enable.app_error",
- "translation": ""
+ "translation": "We couldn't enable the access token"
},
{
"id": "store.sql_webhooks.analytics_incoming_count.app_error",
@@ -6568,7 +6044,7 @@
},
{
"id": "utils.config.add_client_locale.app_error",
- "translation": "Unable to load mattermost configuration file: Adding DefaultClientLocale to AvailableLocales."
+ "translation": "Unable to load mattermost configuration file: Adding DefaultClientLocale to AvailableLocales."
},
{
"id": "utils.config.load_config.decoding.panic",
@@ -6584,15 +6060,15 @@
},
{
"id": "utils.config.supported_available_locales.app_error",
- "translation": "Unable to load mattermost configuration file: AvailableLocales must include DefaultClientLocale. Setting AvailableLocales to all locales as default value."
+ "translation": "Unable to load mattermost configuration file: AvailableLocales must include DefaultClientLocale. Setting AvailableLocales to all locales as default value."
},
{
"id": "utils.config.supported_client_locale.app_error",
- "translation": "Unable to load mattermost configuration file: DefaultClientLocale must be one of the supported locales. Setting DefaultClientLocale to en as default value."
+ "translation": "Unable to load mattermost configuration file: DefaultClientLocale must be one of the supported locales. Setting DefaultClientLocale to en as default value."
},
{
"id": "utils.config.supported_server_locale.app_error",
- "translation": "Unable to load mattermost configuration file: DefaultServerLocale must be one of the supported locales. Setting DefaultServerLocale to en as default value."
+ "translation": "Unable to load mattermost configuration file: DefaultServerLocale must be one of the supported locales. Setting DefaultServerLocale to en as default value."
},
{
"id": "utils.file.list_directory.local.app_error",
@@ -6680,7 +6156,7 @@
},
{
"id": "web.get_access_token.internal_saving.app_error",
- "translation": ""
+ "translation": "We couldn't update the user access data."
},
{
"id": "web.incoming_webhook.channel.app_error",
diff --git a/migrations/scheduler.go b/migrations/scheduler.go
index 8a7ac30d0..5778c5cb3 100644
--- a/migrations/scheduler.go
+++ b/migrations/scheduler.go
@@ -59,7 +59,7 @@ func (scheduler *Scheduler) ScheduleJob(cfg *model.Config, pendingJobs bool, las
if state == MIGRATION_STATE_IN_PROGRESS {
// Check the migration job isn't wedged.
- if job != nil && job.LastActivityAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS {
+ if job != nil && job.LastActivityAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS && job.CreateAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS {
mlog.Warn("Job appears to be wedged. Rescheduling another instance.", mlog.String("scheduler", scheduler.Name()), mlog.String("wedged_job_id", job.Id), mlog.String("migration_key", key))
if err := scheduler.App.Jobs.SetJobError(job, nil); err != nil {
mlog.Error("Worker: Failed to set job error", mlog.String("scheduler", scheduler.Name()), mlog.String("job_id", job.Id), mlog.String("error", err.Error()))
diff --git a/model/config.go b/model/config.go
index ce66f2f05..e6bd04dfc 100644
--- a/model/config.go
+++ b/model/config.go
@@ -605,15 +605,16 @@ type SSOSettings struct {
}
type SqlSettings struct {
- DriverName *string
- DataSource *string
- DataSourceReplicas []string
- DataSourceSearchReplicas []string
- MaxIdleConns *int
- MaxOpenConns *int
- Trace bool
- AtRestEncryptKey string
- QueryTimeout *int
+ DriverName *string
+ DataSource *string
+ DataSourceReplicas []string
+ DataSourceSearchReplicas []string
+ MaxIdleConns *int
+ ConnMaxLifetimeMilliseconds *int
+ MaxOpenConns *int
+ Trace bool
+ AtRestEncryptKey string
+ QueryTimeout *int
}
func (s *SqlSettings) SetDefaults() {
@@ -637,6 +638,10 @@ func (s *SqlSettings) SetDefaults() {
s.MaxOpenConns = NewInt(300)
}
+ if s.ConnMaxLifetimeMilliseconds == nil {
+ s.ConnMaxLifetimeMilliseconds = NewInt(3600000)
+ }
+
if s.QueryTimeout == nil {
s.QueryTimeout = NewInt(30)
}
@@ -2069,6 +2074,10 @@ func (ss *SqlSettings) isValid() *AppError {
return NewAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "", http.StatusBadRequest)
}
+ if *ss.ConnMaxLifetimeMilliseconds < 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_conn_max_lifetime_milliseconds.app_error", nil, "", http.StatusBadRequest)
+ }
+
if *ss.QueryTimeout <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest)
}
diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go
index e062d41d1..f82a00858 100644
--- a/store/sqlstore/channel_store.go
+++ b/store/sqlstore/channel_store.go
@@ -1617,6 +1617,8 @@ func (s SqlChannelStore) buildFulltextClause(term string) (fulltextClause, fullt
// Prepare the FULLTEXT portion of the query.
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
+ fulltextTerm = strings.Replace(fulltextTerm, "|", "", -1)
+
splitTerm := strings.Fields(fulltextTerm)
for i, t := range strings.Fields(fulltextTerm) {
if i == len(splitTerm)-1 {
diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go
index 1e6bdcba1..6c49d91fb 100644
--- a/store/sqlstore/supplier.go
+++ b/store/sqlstore/supplier.go
@@ -28,7 +28,6 @@ import (
const (
INDEX_TYPE_FULL_TEXT = "full_text"
INDEX_TYPE_DEFAULT = "default"
- MAX_DB_CONN_LIFETIME = 60
DB_PING_ATTEMPTS = 18
DB_PING_TIMEOUT_SECS = 10
)
@@ -218,7 +217,7 @@ func setupConnection(con_type string, dataSource string, settings *model.SqlSett
db.SetMaxIdleConns(*settings.MaxIdleConns)
db.SetMaxOpenConns(*settings.MaxOpenConns)
- db.SetConnMaxLifetime(time.Duration(MAX_DB_CONN_LIFETIME) * time.Minute)
+ db.SetConnMaxLifetime(time.Duration(*settings.ConnMaxLifetimeMilliseconds) * time.Millisecond)
var dbmap *gorp.DbMap
diff --git a/store/sqlstore/supplier_test.go b/store/sqlstore/supplier_test.go
index 5aacfbe6a..3835bd01f 100644
--- a/store/sqlstore/supplier_test.go
+++ b/store/sqlstore/supplier_test.go
@@ -73,17 +73,19 @@ func TestGetReplica(t *testing.T) {
driverName := model.DATABASE_DRIVER_SQLITE
dataSource := ":memory:"
maxIdleConns := 1
+ connMaxLifetimeMilliseconds := 3600000
maxOpenConns := 1
queryTimeout := 5
settings := model.SqlSettings{
- DriverName: &driverName,
- DataSource: &dataSource,
- MaxIdleConns: &maxIdleConns,
- MaxOpenConns: &maxOpenConns,
- QueryTimeout: &queryTimeout,
- DataSourceReplicas: testCase.DataSourceReplicas,
- DataSourceSearchReplicas: testCase.DataSourceSearchReplicas,
+ DriverName: &driverName,
+ DataSource: &dataSource,
+ MaxIdleConns: &maxIdleConns,
+ ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds,
+ MaxOpenConns: &maxOpenConns,
+ QueryTimeout: &queryTimeout,
+ DataSourceReplicas: testCase.DataSourceReplicas,
+ DataSourceSearchReplicas: testCase.DataSourceSearchReplicas,
}
supplier := sqlstore.NewSqlSupplier(settings, nil)
@@ -209,17 +211,19 @@ func TestGetAllConns(t *testing.T) {
driverName := model.DATABASE_DRIVER_SQLITE
dataSource := ":memory:"
maxIdleConns := 1
+ connMaxLifetimeMilliseconds := 3600000
maxOpenConns := 1
queryTimeout := 5
settings := model.SqlSettings{
- DriverName: &driverName,
- DataSource: &dataSource,
- MaxIdleConns: &maxIdleConns,
- MaxOpenConns: &maxOpenConns,
- QueryTimeout: &queryTimeout,
- DataSourceReplicas: testCase.DataSourceReplicas,
- DataSourceSearchReplicas: testCase.DataSourceSearchReplicas,
+ DriverName: &driverName,
+ DataSource: &dataSource,
+ MaxIdleConns: &maxIdleConns,
+ ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds,
+ MaxOpenConns: &maxOpenConns,
+ QueryTimeout: &queryTimeout,
+ DataSourceReplicas: testCase.DataSourceReplicas,
+ DataSourceSearchReplicas: testCase.DataSourceSearchReplicas,
}
supplier := sqlstore.NewSqlSupplier(settings, nil)
diff --git a/store/sqlstore/user_access_token_store.go b/store/sqlstore/user_access_token_store.go
index b90ba773f..5df087026 100644
--- a/store/sqlstore/user_access_token_store.go
+++ b/store/sqlstore/user_access_token_store.go
@@ -245,7 +245,7 @@ func (s SqlUserAccessTokenStore) UpdateTokenDisable(tokenId string) store.StoreC
return store.Do(func(result *store.StoreResult) {
transaction, err := s.GetMaster().Begin()
if err != nil {
- result.Err = model.NewAppError("SqlUserAccessTokenStore.UpdateTokenDisable", "store.sql_user_access_token.update_token_disble.app_error", nil, err.Error(), http.StatusInternalServerError)
+ result.Err = model.NewAppError("SqlUserAccessTokenStore.UpdateTokenDisable", "store.sql_user_access_token.update_token_disable.app_error", nil, err.Error(), http.StatusInternalServerError)
} else {
if extrasResult := s.deleteSessionsAndDisableToken(transaction, tokenId); extrasResult.Err != nil {
*result = extrasResult
diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go
index b033e9c98..ccf4b1c59 100644
--- a/store/storetest/channel_store.go
+++ b/store/storetest/channel_store.go
@@ -2028,6 +2028,19 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) {
t.Fatal("wrong channel returned")
}
}
+
+ if result := <-search(o1.TeamId, "town square |"); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ channels := result.Data.(*model.ChannelList)
+ if len(*channels) != 1 {
+ t.Fatal("should return 1 channel")
+ }
+
+ if (*channels)[0].Name != o9.Name {
+ t.Fatal("wrong channel returned")
+ }
+ }
})
}
}
diff --git a/store/storetest/docker.go b/store/storetest/docker.go
index f3830a6fe..f5b8807d0 100644
--- a/store/storetest/docker.go
+++ b/store/storetest/docker.go
@@ -76,17 +76,19 @@ func NewPostgreSQLContainer() (*RunningContainer, *model.SqlSettings, error) {
func databaseSettings(driver, dataSource string) *model.SqlSettings {
settings := &model.SqlSettings{
- DriverName: &driver,
- DataSource: &dataSource,
- DataSourceReplicas: []string{},
- DataSourceSearchReplicas: []string{},
- MaxIdleConns: new(int),
- MaxOpenConns: new(int),
- Trace: false,
- AtRestEncryptKey: model.NewRandomString(32),
- QueryTimeout: new(int),
+ DriverName: &driver,
+ DataSource: &dataSource,
+ DataSourceReplicas: []string{},
+ DataSourceSearchReplicas: []string{},
+ MaxIdleConns: new(int),
+ ConnMaxLifetimeMilliseconds: new(int),
+ MaxOpenConns: new(int),
+ Trace: false,
+ AtRestEncryptKey: model.NewRandomString(32),
+ QueryTimeout: new(int),
}
*settings.MaxIdleConns = 10
+ *settings.ConnMaxLifetimeMilliseconds = 3600000
*settings.MaxOpenConns = 100
*settings.QueryTimeout = 10
return settings
diff --git a/utils/subpath.go b/utils/subpath.go
index cddc90fa4..be06a73ef 100644
--- a/utils/subpath.go
+++ b/utils/subpath.go
@@ -89,14 +89,14 @@ func UpdateAssetsSubpath(subpath string) error {
return errors.Wrapf(err, "failed to update root.html with subpath %s", subpath)
}
- // Rewrite the *.css references to `/static/*` (or a previously rewritten subpath).
+ // Rewrite the manifest.json and *.css references to `/static/*` (or a previously rewritten subpath).
err = filepath.Walk(staticDir, func(walkPath string, info os.FileInfo, err error) error {
- if filepath.Ext(walkPath) == ".css" {
- if oldCss, err := ioutil.ReadFile(walkPath); err != nil {
+ if filepath.Base(walkPath) == "manifest.json" || filepath.Ext(walkPath) == ".css" {
+ if old, err := ioutil.ReadFile(walkPath); err != nil {
return errors.Wrapf(err, "failed to open %s", walkPath)
} else {
- newCss := strings.Replace(string(oldCss), pathToReplace, newPath, -1)
- if err = ioutil.WriteFile(walkPath, []byte(newCss), 0); err != nil {
+ new := strings.Replace(string(old), pathToReplace, newPath, -1)
+ if err = ioutil.WriteFile(walkPath, []byte(new), 0); err != nil {
return errors.Wrapf(err, "failed to update %s with subpath %s", walkPath, subpath)
}
}
diff --git a/utils/subpath_test.go b/utils/subpath_test.go
index ee518d5f6..6e417e1c5 100644
--- a/utils/subpath_test.go
+++ b/utils/subpath_test.go
@@ -33,52 +33,64 @@ func TestUpdateAssetsSubpath(t *testing.T) {
require.NoError(t, err)
testCases := []struct {
- Description string
- RootHTML string
- MainCSS string
- Subpath string
- ExpectedRootHTML string
- ExpectedMainCSS string
+ Description string
+ RootHTML string
+ MainCSS string
+ ManifestJSON string
+ Subpath string
+ ExpectedRootHTML string
+ ExpectedMainCSS string
+ ExpectedManifestJSON string
}{
{
"no changes required, empty subpath provided",
baseRootHtml,
baseCss,
+ baseManifestJson,
"",
baseRootHtml,
baseCss,
+ baseManifestJson,
},
{
"no changes required",
baseRootHtml,
baseCss,
+ baseManifestJson,
"/",
baseRootHtml,
baseCss,
+ baseManifestJson,
},
{
"subpath",
baseRootHtml,
baseCss,
+ baseManifestJson,
"/subpath",
subpathRootHtml,
subpathCss,
+ subpathManifestJson,
},
{
"new subpath from old",
subpathRootHtml,
subpathCss,
+ subpathManifestJson,
"/nested/subpath",
newSubpathRootHtml,
newSubpathCss,
+ newSubpathManifestJson,
},
{
"resetting to /",
subpathRootHtml,
subpathCss,
+ baseManifestJson,
"/",
resetRootHtml,
baseCss,
+ baseManifestJson,
},
}
@@ -86,6 +98,7 @@ func TestUpdateAssetsSubpath(t *testing.T) {
t.Run(testCase.Description, func(t *testing.T) {
ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "root.html"), []byte(testCase.RootHTML), 0700)
ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "main.css"), []byte(testCase.MainCSS), 0700)
+ ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "manifest.json"), []byte(testCase.ManifestJSON), 0700)
err := utils.UpdateAssetsSubpath(testCase.Subpath)
require.NoError(t, err)
@@ -97,6 +110,9 @@ func TestUpdateAssetsSubpath(t *testing.T) {
require.NoError(t, err)
require.Equal(t, testCase.ExpectedMainCSS, string(contents))
+ contents, err = ioutil.ReadFile(filepath.Join(tempDir, model.CLIENT_DIR, "manifest.json"))
+ require.NoError(t, err)
+ require.Equal(t, testCase.ExpectedManifestJSON, string(contents))
})
}
})
@@ -190,3 +206,207 @@ const newSubpathRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=
const newSubpathCss = `@font-face{font-family:FontAwesome;src:url(/nested/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/nested/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/nested/subpath/static/files/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/nested/subpath/static/files/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/nested/subpath/static/files/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/nested/subpath/static/files/677433a0892aaed7b7d2628c313c9775.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}`
const resetRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ 'unsafe-eval' 'sha256-VFw7U/t/OI+I9YMja3c2GDwEQbnlOq/L5+GealgesK8='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/static/'</script> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
+
+const baseManifestJson = `{
+ "icons": [
+ {
+ "src": "/static/icon_96x96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_32x32.png",
+ "sizes": "32x32",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_16x16.png",
+ "sizes": "16x16",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_76x76.png",
+ "sizes": "76x76",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_72x72.png",
+ "sizes": "72x72",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_60x60.png",
+ "sizes": "60x60",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_57x57.png",
+ "sizes": "57x57",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_152x152.png",
+ "sizes": "152x152",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_144x144.png",
+ "sizes": "144x144",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_120x120.png",
+ "sizes": "120x120",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/icon_192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ }
+ ],
+ "name": "Mattermost",
+ "short_name": "Mattermost",
+ "orientation": "any",
+ "display": "standalone",
+ "start_url": ".",
+ "description": "Mattermost is an open source, self-hosted Slack-alternative",
+ "background_color": "#ffffff"
+}
+`
+
+const subpathManifestJson = `{
+ "icons": [
+ {
+ "src": "/subpath/static/icon_96x96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_32x32.png",
+ "sizes": "32x32",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_16x16.png",
+ "sizes": "16x16",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_76x76.png",
+ "sizes": "76x76",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_72x72.png",
+ "sizes": "72x72",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_60x60.png",
+ "sizes": "60x60",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_57x57.png",
+ "sizes": "57x57",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_152x152.png",
+ "sizes": "152x152",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_144x144.png",
+ "sizes": "144x144",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_120x120.png",
+ "sizes": "120x120",
+ "type": "image/png"
+ },
+ {
+ "src": "/subpath/static/icon_192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ }
+ ],
+ "name": "Mattermost",
+ "short_name": "Mattermost",
+ "orientation": "any",
+ "display": "standalone",
+ "start_url": ".",
+ "description": "Mattermost is an open source, self-hosted Slack-alternative",
+ "background_color": "#ffffff"
+}
+`
+
+const newSubpathManifestJson = `{
+ "icons": [
+ {
+ "src": "/nested/subpath/static/icon_96x96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_32x32.png",
+ "sizes": "32x32",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_16x16.png",
+ "sizes": "16x16",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_76x76.png",
+ "sizes": "76x76",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_72x72.png",
+ "sizes": "72x72",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_60x60.png",
+ "sizes": "60x60",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_57x57.png",
+ "sizes": "57x57",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_152x152.png",
+ "sizes": "152x152",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_144x144.png",
+ "sizes": "144x144",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_120x120.png",
+ "sizes": "120x120",
+ "type": "image/png"
+ },
+ {
+ "src": "/nested/subpath/static/icon_192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ }
+ ],
+ "name": "Mattermost",
+ "short_name": "Mattermost",
+ "orientation": "any",
+ "display": "standalone",
+ "start_url": ".",
+ "description": "Mattermost is an open source, self-hosted Slack-alternative",
+ "background_color": "#ffffff"
+}
+`