merging
Этот коммит содержится в:
12
api/admin.go
12
api/admin.go
@@ -27,6 +27,7 @@ func InitAdmin(r *mux.Router) {
|
||||
sr.Handle("/client_props", ApiAppHandler(getClientConfig)).Methods("GET")
|
||||
sr.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
|
||||
sr.Handle("/analytics/{id:[A-Za-z0-9]+}/{name:[A-Za-z0-9_]+}", ApiAppHandler(getAnalytics)).Methods("GET")
|
||||
sr.Handle("/analytics/{name:[A-Za-z0-9_]+}", ApiAppHandler(getAnalytics)).Methods("GET")
|
||||
}
|
||||
|
||||
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -153,13 +154,15 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
name := params["name"]
|
||||
|
||||
if name == "standard" {
|
||||
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 3)
|
||||
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 4)
|
||||
rows[0] = &model.AnalyticsRow{"channel_open_count", 0}
|
||||
rows[1] = &model.AnalyticsRow{"channel_private_count", 0}
|
||||
rows[2] = &model.AnalyticsRow{"post_count", 0}
|
||||
rows[3] = &model.AnalyticsRow{"unique_user_count", 0}
|
||||
openChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
|
||||
privateChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
|
||||
postChan := Srv.Store.Post().AnalyticsPostCount(teamId)
|
||||
userChan := Srv.Store.User().AnalyticsUniqueUserCount(teamId)
|
||||
|
||||
if r := <-openChan; r.Err != nil {
|
||||
c.Err = r.Err
|
||||
@@ -182,6 +185,13 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
rows[2].Value = float64(r.Data.(int64))
|
||||
}
|
||||
|
||||
if r := <-userChan; r.Err != nil {
|
||||
c.Err = r.Err
|
||||
return
|
||||
} else {
|
||||
rows[3].Value = float64(r.Data.(int64))
|
||||
}
|
||||
|
||||
w.Write([]byte(rows.ToJson()))
|
||||
} else if name == "post_counts_day" {
|
||||
if r := <-Srv.Store.Post().AnalyticsPostCountsByDay(teamId); r.Err != nil {
|
||||
|
||||
@@ -151,7 +151,7 @@ func TestEmailTest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAnalyticsStandard(t *testing.T) {
|
||||
func TestGetTeamAnalyticsStandard(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
@@ -169,7 +169,7 @@ func TestGetAnalyticsStandard(t *testing.T) {
|
||||
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
|
||||
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
|
||||
|
||||
if _, err := Client.GetAnalytics(team.Id, "standard"); err == nil {
|
||||
if _, err := Client.GetTeamAnalytics(team.Id, "standard"); err == nil {
|
||||
t.Fatal("Shouldn't have permissions")
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestGetAnalyticsStandard(t *testing.T) {
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
if result, err := Client.GetAnalytics(team.Id, "standard"); err != nil {
|
||||
if result, err := Client.GetTeamAnalytics(team.Id, "standard"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows := result.Data.(model.AnalyticsRows)
|
||||
@@ -214,6 +214,62 @@ func TestGetAnalyticsStandard(t *testing.T) {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[3].Name != "unique_user_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[3].Value != 1 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.GetSystemAnalytics("standard"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows := result.Data.(model.AnalyticsRows)
|
||||
|
||||
if rows[0].Name != "channel_open_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[0].Value < 2 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[1].Name != "channel_private_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[1].Value == 0 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[2].Name != "post_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[2].Value == 0 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[3].Name != "unique_user_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[3].Value == 0 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +295,7 @@ func TestGetPostCount(t *testing.T) {
|
||||
Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId",
|
||||
map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
|
||||
|
||||
if _, err := Client.GetAnalytics(team.Id, "post_counts_day"); err == nil {
|
||||
if _, err := Client.GetTeamAnalytics(team.Id, "post_counts_day"); err == nil {
|
||||
t.Fatal("Shouldn't have permissions")
|
||||
}
|
||||
|
||||
@@ -250,7 +306,7 @@ func TestGetPostCount(t *testing.T) {
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
if result, err := Client.GetAnalytics(team.Id, "post_counts_day"); err != nil {
|
||||
if result, err := Client.GetTeamAnalytics(team.Id, "post_counts_day"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows := result.Data.(model.AnalyticsRows)
|
||||
@@ -284,7 +340,7 @@ func TestUserCountsWithPostsByDay(t *testing.T) {
|
||||
Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId",
|
||||
map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())})
|
||||
|
||||
if _, err := Client.GetAnalytics(team.Id, "user_counts_with_posts_day"); err == nil {
|
||||
if _, err := Client.GetTeamAnalytics(team.Id, "user_counts_with_posts_day"); err == nil {
|
||||
t.Fatal("Shouldn't have permissions")
|
||||
}
|
||||
|
||||
@@ -295,7 +351,7 @@ func TestUserCountsWithPostsByDay(t *testing.T) {
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
if result, err := Client.GetAnalytics(team.Id, "user_counts_with_posts_day"); err != nil {
|
||||
if result, err := Client.GetTeamAnalytics(team.Id, "user_counts_with_posts_day"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows := result.Data.(model.AnalyticsRows)
|
||||
|
||||
12
api/api.go
12
api/api.go
@@ -19,17 +19,25 @@ var ServerTemplates *template.Template
|
||||
|
||||
type ServerTemplatePage Page
|
||||
|
||||
func NewServerTemplatePage(templateName string) *ServerTemplatePage {
|
||||
func NewServerTemplatePage(templateName, locale string) *ServerTemplatePage {
|
||||
return &ServerTemplatePage{
|
||||
TemplateName: templateName,
|
||||
Props: make(map[string]string),
|
||||
Extra: make(map[string]string),
|
||||
Html: make(map[string]template.HTML),
|
||||
ClientCfg: utils.ClientCfg,
|
||||
Locale: model.DEFAULT_LOCALE,
|
||||
Locale: locale,
|
||||
}
|
||||
}
|
||||
|
||||
func (me *ServerTemplatePage) Render() string {
|
||||
var text bytes.Buffer
|
||||
|
||||
T := utils.GetUserTranslations(me.Locale)
|
||||
me.Props["Footer"] = T("api.templates.email_footer")
|
||||
me.Html["EmailInfo"] = template.HTML(T("api.templates.email_info",
|
||||
map[string]interface{}{"FeedbackEmail": me.ClientCfg["FeedbackEmail"], "SiteName": me.ClientCfg["SiteName"]}))
|
||||
|
||||
if err := ServerTemplates.ExecuteTemplate(&text, me.TemplateName, me); err != nil {
|
||||
l4g.Error(utils.T("api.api.render.error"), me.TemplateName, err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -37,6 +38,8 @@ type Context struct {
|
||||
type Page struct {
|
||||
TemplateName string
|
||||
Props map[string]string
|
||||
Extra map[string]string
|
||||
Html map[string]template.HTML
|
||||
ClientCfg map[string]string
|
||||
ClientLicense map[string]string
|
||||
User *model.User
|
||||
@@ -507,6 +510,10 @@ func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request)
|
||||
props["SiteURL"] = GetProtocol(r) + "://" + r.Host
|
||||
}
|
||||
|
||||
T, _ := utils.GetTranslationsAndLocale(w, r)
|
||||
props["Title"] = T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
|
||||
props["Link"] = T("api.templates.error.link")
|
||||
|
||||
w.WriteHeader(err.StatusCode)
|
||||
ServerTemplates.ExecuteTemplate(w, "error.html", Page{Props: props, ClientCfg: utils.ClientCfg})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/platform/model"
|
||||
@@ -63,6 +64,18 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if success, licenseStr := utils.ValidateLicense(data); success {
|
||||
license = model.LicenseFromJson(strings.NewReader(licenseStr))
|
||||
|
||||
if result := <-Srv.Store.User().AnalyticsUniqueUserCount(""); result.Err != nil {
|
||||
c.Err = model.NewAppError("addLicense", "Unable to count total unique users.", fmt.Sprintf("err=%v", result.Err.Error()))
|
||||
return
|
||||
} else {
|
||||
uniqueUserCount := result.Data.(int64)
|
||||
|
||||
if uniqueUserCount > int64(*license.Features.Users) {
|
||||
c.Err = model.NewAppError("addLicense", fmt.Sprintf("This license only supports %d users, when your system has %d unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics.", *license.Features.Users, uniqueUserCount), "")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ok := utils.SetLicense(license); !ok {
|
||||
c.LogAudit("failed - expired or non-started license")
|
||||
c.Err = model.NewLocAppError("addLicense", "api.license.add_license.expired.app_error", nil, "")
|
||||
|
||||
35
api/post.go
35
api/post.go
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
@@ -593,28 +594,26 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
|
||||
channelName = channel.DisplayName
|
||||
}
|
||||
|
||||
subjectPage := NewServerTemplatePage("post_subject")
|
||||
subjectPage.Props["SiteURL"] = c.GetSiteURL()
|
||||
subjectPage.Props["TeamDisplayName"] = team.DisplayName
|
||||
subjectPage.Props["SubjectText"] = subjectText
|
||||
subjectPage.Props["Month"] = tm.Month().String()[:3]
|
||||
subjectPage.Props["Day"] = fmt.Sprintf("%d", tm.Day())
|
||||
subjectPage.Props["Year"] = fmt.Sprintf("%d", tm.Year())
|
||||
month := userLocale(tm.Month().String())
|
||||
day := fmt.Sprintf("%d", tm.Day())
|
||||
year := fmt.Sprintf("%d", tm.Year())
|
||||
zone, _ := tm.Zone()
|
||||
|
||||
bodyPage := NewServerTemplatePage("post_body")
|
||||
subjectPage := NewServerTemplatePage("post_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = userLocale("api.templates.post_subject",
|
||||
map[string]interface{}{"SubjectText": subjectText, "TeamDisplayName": team.DisplayName,
|
||||
"Month": month[:3], "Day": day, "Year": year})
|
||||
|
||||
bodyPage := NewServerTemplatePage("post_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = c.GetSiteURL()
|
||||
bodyPage.Props["Nickname"] = profileMap[id].FirstName
|
||||
bodyPage.Props["TeamDisplayName"] = team.DisplayName
|
||||
bodyPage.Props["ChannelName"] = channelName
|
||||
bodyPage.Props["BodyText"] = bodyText
|
||||
bodyPage.Props["SenderName"] = senderName
|
||||
bodyPage.Props["Hour"] = fmt.Sprintf("%02d", tm.Hour())
|
||||
bodyPage.Props["Minute"] = fmt.Sprintf("%02d", tm.Minute())
|
||||
bodyPage.Props["Month"] = tm.Month().String()[:3]
|
||||
bodyPage.Props["Day"] = fmt.Sprintf("%d", tm.Day())
|
||||
bodyPage.Props["TimeZone"], _ = tm.Zone()
|
||||
bodyPage.Props["PostMessage"] = model.ClearMentionTags(post.Message)
|
||||
bodyPage.Props["TeamLink"] = teamURL + "/channels/" + channel.Name
|
||||
bodyPage.Props["BodyText"] = bodyText
|
||||
bodyPage.Props["Button"] = userLocale("api.templates.post_body.button")
|
||||
bodyPage.Html["Info"] = template.HTML(userLocale("api.templates.post_body.info",
|
||||
map[string]interface{}{"ChannelName": channelName, "SenderName": senderName,
|
||||
"Hour": fmt.Sprintf("%02d", tm.Hour()), "Minute": fmt.Sprintf("%02d", tm.Minute()),
|
||||
"TimeZone": zone, "Month": month, "Day": day}))
|
||||
|
||||
// attempt to fill in a message body if the post doesn't have any text
|
||||
if len(strings.TrimSpace(bodyPage.Props["PostMessage"])) == 0 && len(post.Filenames) > 0 {
|
||||
|
||||
48
api/team.go
48
api/team.go
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -57,10 +58,16 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
subjectPage := NewServerTemplatePage("signup_team_subject")
|
||||
subjectPage.Props["SiteURL"] = c.GetSiteURL()
|
||||
bodyPage := NewServerTemplatePage("signup_team_body")
|
||||
subjectPage := NewServerTemplatePage("signup_team_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.signup_team_subject",
|
||||
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
|
||||
|
||||
bodyPage := NewServerTemplatePage("signup_team_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = c.GetSiteURL()
|
||||
bodyPage.Props["Title"] = c.T("api.templates.signup_team_body.title")
|
||||
bodyPage.Props["Button"] = c.T("api.templates.signup_team_body.button")
|
||||
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.signup_team_body.button",
|
||||
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}))
|
||||
|
||||
props := make(map[string]string)
|
||||
props["email"] = email
|
||||
@@ -427,10 +434,16 @@ func emailTeams(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
subjectPage := NewServerTemplatePage("find_teams_subject")
|
||||
subjectPage.ClientCfg["SiteURL"] = c.GetSiteURL()
|
||||
bodyPage := NewServerTemplatePage("find_teams_body")
|
||||
bodyPage.ClientCfg["SiteURL"] = c.GetSiteURL()
|
||||
siteURL := c.GetSiteURL()
|
||||
subjectPage := NewServerTemplatePage("find_teams_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.find_teams_subject",
|
||||
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
|
||||
|
||||
bodyPage := NewServerTemplatePage("find_teams_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = siteURL
|
||||
bodyPage.Props["Title"] = c.T("api.templates.find_teams_body.title")
|
||||
bodyPage.Props["Found"] = c.T("api.templates.find_teams_body.found")
|
||||
bodyPage.Props["NotFound"] = c.T("api.templates.find_teams_body.not_found")
|
||||
|
||||
if result := <-Srv.Store.Team().GetTeamsForEmail(email); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
@@ -442,7 +455,7 @@ func emailTeams(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
for _, team := range teams {
|
||||
props[team.Name] = c.GetTeamURLFromTeam(team)
|
||||
}
|
||||
bodyPage.Props = props
|
||||
bodyPage.Extra = props
|
||||
|
||||
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||
l4g.Error(utils.T("api.team.email_teams.sending.error"), err)
|
||||
@@ -511,16 +524,19 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
|
||||
senderRole = c.T("api.team.invite_members.member")
|
||||
}
|
||||
|
||||
subjectPage := NewServerTemplatePage("invite_subject")
|
||||
subjectPage.Props["SenderName"] = sender
|
||||
subjectPage.Props["TeamDisplayName"] = team.DisplayName
|
||||
subjectPage := NewServerTemplatePage("invite_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.invite_subject",
|
||||
map[string]interface{}{"SenderName": sender, "TeamDisplayName": team.DisplayName, "SiteName": utils.ClientCfg["SiteName"]})
|
||||
|
||||
bodyPage := NewServerTemplatePage("invite_body")
|
||||
bodyPage := NewServerTemplatePage("invite_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = c.GetSiteURL()
|
||||
bodyPage.Props["TeamURL"] = c.GetTeamURL()
|
||||
bodyPage.Props["TeamDisplayName"] = team.DisplayName
|
||||
bodyPage.Props["SenderName"] = sender
|
||||
bodyPage.Props["SenderStatus"] = senderRole
|
||||
bodyPage.Props["Title"] = c.T("api.templates.invite_body.title")
|
||||
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.invite_body.info",
|
||||
map[string]interface{}{"SenderStatus": senderRole, "SenderName": sender, "TeamDisplayName": team.DisplayName}))
|
||||
bodyPage.Props["Button"] = c.T("api.templates.invite_body.button")
|
||||
bodyPage.Html["ExtraInfo"] = template.HTML(c.T("api.templates.invite_body.extra_info",
|
||||
map[string]interface{}{"TeamDisplayName": team.DisplayName, "TeamURL": c.GetTeamURL()}))
|
||||
|
||||
props := make(map[string]string)
|
||||
props["email"] = invite
|
||||
props["id"] = team.Id
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You updated your email</h2>
|
||||
<p>You email address for {{.Props.TeamDisplayName}} has been changed to {{.Props.NewEmail}}.<br>If you did not make this change, please contact the system administrator.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Props.Info}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "email_change_subject"}}[{{.ClientCfg.SiteName}}] Your email address has changed for {{.Props.TeamDisplayName}}{{end}}
|
||||
{{define "email_change_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You updated your email</h2>
|
||||
<p>To finish updating your email address for {{.Props.TeamDisplayName}}, please click the link below to confirm this is the right address.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Props.Info}}</p>
|
||||
<p style="margin: 20px 0 15px">
|
||||
<a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Verify Email</a>
|
||||
<a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.VerifyButton}}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "email_change_verify_subject"}}[{{.ClientCfg.SiteName}}] Verify new email address for {{.Props.TeamDisplayName}}{{end}}
|
||||
{{define "email_change_verify_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</p>
|
||||
<p style="padding: 0 50px;">
|
||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||
To change your notification preferences, log in to your team site and go to Account Settings > Notifications.
|
||||
{{.Props.Footer}}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{{define "email_info"}}
|
||||
|
||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||
Best wishes,<br>
|
||||
The {{.ClientCfg.SiteName}} Team<br>
|
||||
{{.Html.EmailInfo}}
|
||||
</td>
|
||||
|
||||
{{end}}
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
<div class="container-fluid">
|
||||
<div class="error__container">
|
||||
<div class="error__icon"><i class="fa fa-exclamation-triangle"></i></div>
|
||||
<h2>{{ .ClientCfg.SiteName }} needs your help:</h2>
|
||||
<h2>{{.Props.Title}}</h2>
|
||||
<p>{{ .Props.Message }}</p>
|
||||
<a href="{{.Props.SiteURL}}">Go back to team site</a>
|
||||
<a href="{{.Props.SiteURL}}">{{.Props.Link}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">Finding teams</h2>
|
||||
<p>{{ if .Props }}
|
||||
Your request to find teams associated with your email found the following:<br>
|
||||
{{range $index, $element := .Props}}
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{ if .Extra }}
|
||||
{{.Props.Found}}<br>
|
||||
{{range $index, $element := .Extra}}
|
||||
<a href="{{ $element }}" style="text-decoration: none; color:#2389D7;">{{ $index }}</a><br>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
We could not find any teams for the given email.
|
||||
{{.Props.NotFound}}
|
||||
{{ end }}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "find_teams_subject"}}Your {{ .ClientCfg.SiteName }} Teams{{end}}
|
||||
{{define "find_teams_subject"}}{{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You've been invited</h2>
|
||||
<p>The team {{.Props.SenderStatus}} <strong>{{.Props.SenderName}}</strong>, has invited you to join <strong>{{.Props.TeamDisplayName}}</strong>.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Html.Info}}</p>
|
||||
<p style="margin: 30px 0 15px">
|
||||
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Join Team</a>
|
||||
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
|
||||
</p>
|
||||
<br/>
|
||||
<p>Mattermost lets you share messages and files from your PC or phone, with instant search and archiving. After you’ve joined <strong>{{.Props.TeamDisplayName}}</strong>, you can sign-in to your new team and access these features anytime from the web address:<br/><br/><a href="{{.Props.TeamURL}}">{{.Props.TeamURL}}</a></p>
|
||||
<p>{{.Html.ExtraInfo}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "invite_subject"}}{{ .Props.SenderName }} invited you to join {{ .Props.TeamDisplayName }} Team on {{.ClientCfg.SiteName}}{{end}}
|
||||
{{define "invite_subject"}}{{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You updated your password</h2>
|
||||
<p>Your password has been updated for {{.Props.TeamDisplayName}} on {{ .Props.TeamURL }} by {{.Props.Method}}.<br>If this change wasn't initiated by you, please contact your system administrator.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Html.Info}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "password_change_subject"}}Your password has been updated for {{.Props.TeamDisplayName}} on {{ .ClientCfg.SiteName }}{{end}}
|
||||
{{define "password_change_subject"}}{{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You were mentioned</h2>
|
||||
<p>CHANNEL: {{.Props.ChannelName}}<br>{{.Props.SenderName}} - {{.Props.Hour}}:{{.Props.Minute}} {{.Props.TimeZone}}, {{.Props.Month}} {{.Props.Day}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.BodyText}}</h2>
|
||||
<p>{{.Html.Info}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p>
|
||||
<p style="margin: 20px 0 15px">
|
||||
<a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Go To Channel</a>
|
||||
<a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "post_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.TeamDisplayName}} Team Notifications for {{.Props.Month}} {{.Props.Day}}, {{.Props.Year}}{{end}}
|
||||
{{define "post_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You requested a password reset</h2>
|
||||
<p>To change your password, click "Reset Password" below.<br>If you did not mean to reset your password, please ignore this email and your password will remain the same.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Html.Info}}</p>
|
||||
<p style="margin: 20px 0 15px">
|
||||
<a href="{{.Props.ResetUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Reset Password</a>
|
||||
<a href="{{.Props.ResetUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "reset_subject"}}Reset your password{{end}}
|
||||
{{define "reset_subject"}}{{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You updated your sign-in method</h2>
|
||||
<p>You updated your sign-in method for {{.Props.TeamDisplayName}} on {{ .Props.TeamURL }} to {{.Props.Method}}.<br>If this change wasn't initiated by you, please contact your system administrator.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Html.Info}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "signin_change_subject"}}You updated your sign-in method for {{.Props.TeamDisplayName}} on {{ .ClientCfg.SiteName }}{{end}}
|
||||
{{define "signin_change_subject"}}{{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">Thanks for creating a team!</h2>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p style="margin: 20px 0 25px">
|
||||
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Set up your team</a>
|
||||
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
|
||||
</p>
|
||||
{{ .ClientCfg.SiteName }} is one place for all your team communication, searchable and available anywhere.<br>You'll get more out of {{ .ClientCfg.SiteName }} when your team is in constant communication--let's get them on board.<br></p>
|
||||
{{.Html.Info}}<br></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "signup_team_subject"}}{{ .ClientCfg.SiteName }} Team Setup{{end}}
|
||||
{{define "signup_team_subject"}}{{.Props.Subject}}{{end}}
|
||||
@@ -17,10 +17,10 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You've joined the {{ .Props.TeamDisplayName }} team</h2>
|
||||
<p>Please verify your email address by clicking below.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Props.Info}}</p>
|
||||
<p style="margin: 20px 0 15px">
|
||||
<a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Verify Email</a>
|
||||
<a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "verify_subject"}}[{{ .Props.TeamDisplayName }} {{ .ClientCfg.SiteName }}] Email Verification{{end}}
|
||||
{{define "verify_subject"}}{{.Props.Subject}}{{end}}
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
{{if .Props.VerifyUrl }}
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">You've joined the {{ .Props.TeamDisplayName }} team</h2>
|
||||
<p>Please verify your email address by clicking below.</p>
|
||||
<h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
|
||||
<p>{{.Props.Info}}</p>
|
||||
<p style="margin: 20px 0 15px">
|
||||
<a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Verify Email</a>
|
||||
<a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||
<h2 style="font-weight: normal; margin-top: 25px; line-height: 1.5;">You can sign-in to your new team from the web address:</h2>
|
||||
<h2 style="font-weight: normal; margin-top: 25px; line-height: 1.5;">{{.Props.Info2}}</h2>
|
||||
<a href="{{.Props.TeamURL}}">{{.Props.TeamURL}}</a>
|
||||
<p>Mattermost lets you share messages and files from your PC or phone, with instant search and archiving.</p>
|
||||
<p>{{.Props.Info3}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{define "welcome_subject"}}You joined {{ .Props.TeamDisplayName }}{{end}}
|
||||
{{define "welcome_subject"}}{{.Props.Subject}}{{end}}
|
||||
|
||||
121
api/user.go
121
api/user.go
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/mattermost/platform/utils"
|
||||
"github.com/mssola/user_agent"
|
||||
"hash/fnv"
|
||||
"html/template"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
@@ -134,7 +135,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if sendWelcomeEmail {
|
||||
sendWelcomeEmailAndForget(ruser.Id, ruser.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team), ruser.EmailVerified)
|
||||
sendWelcomeEmailAndForget(c, ruser.Id, ruser.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team), ruser.EmailVerified)
|
||||
}
|
||||
|
||||
w.Write([]byte(ruser.ToJson()))
|
||||
@@ -308,13 +309,19 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
|
||||
return ruser
|
||||
}
|
||||
|
||||
func sendWelcomeEmailAndForget(userId, email, teamName, teamDisplayName, siteURL, teamURL string, verified bool) {
|
||||
func sendWelcomeEmailAndForget(c *Context, userId, email, teamName, teamDisplayName, siteURL, teamURL string, verified bool) {
|
||||
go func() {
|
||||
|
||||
subjectPage := NewServerTemplatePage("welcome_subject")
|
||||
subjectPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage := NewServerTemplatePage("welcome_body")
|
||||
subjectPage := NewServerTemplatePage("welcome_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"TeamDisplayName": teamDisplayName})
|
||||
|
||||
bodyPage := NewServerTemplatePage("welcome_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = siteURL
|
||||
bodyPage.Props["Title"] = c.T("api.templates.welcome_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName})
|
||||
bodyPage.Props["Info"] = c.T("api.templates.welcome_body.info")
|
||||
bodyPage.Props["Button"] = c.T("api.templates.welcome_body.button")
|
||||
bodyPage.Props["Info2"] = c.T("api.templates.welcome_body.info2")
|
||||
bodyPage.Props["Info3"] = c.T("api.templates.welcome_body.info3")
|
||||
bodyPage.Props["TeamURL"] = teamURL
|
||||
|
||||
if !verified {
|
||||
@@ -367,18 +374,21 @@ func addDirectChannelsAndForget(user *model.User) {
|
||||
}()
|
||||
}
|
||||
|
||||
func SendVerifyEmailAndForget(userId, userEmail, teamName, teamDisplayName, siteURL, teamURL string) {
|
||||
func SendVerifyEmailAndForget(c *Context, userId, userEmail, teamName, teamDisplayName, siteURL, teamURL string) {
|
||||
go func() {
|
||||
|
||||
link := fmt.Sprintf("%s/verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, userEmail)
|
||||
|
||||
subjectPage := NewServerTemplatePage("verify_subject")
|
||||
subjectPage.Props["SiteURL"] = siteURL
|
||||
subjectPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage := NewServerTemplatePage("verify_body")
|
||||
subjectPage := NewServerTemplatePage("verify_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.verify_subject",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
|
||||
|
||||
bodyPage := NewServerTemplatePage("verify_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = siteURL
|
||||
bodyPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage.Props["Title"] = c.T("api.templates.verify_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName})
|
||||
bodyPage.Props["Info"] = c.T("api.templates.verify_body.info")
|
||||
bodyPage.Props["VerifyUrl"] = link
|
||||
bodyPage.Props["Button"] = c.T("api.templates.verify_body.button")
|
||||
|
||||
if err := utils.SendMail(userEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||
l4g.Error(utils.T("api.user.send_verify_email_and_forget.failed.error"), err)
|
||||
@@ -1141,10 +1151,10 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
l4g.Error(tresult.Err.Message)
|
||||
} else {
|
||||
team := tresult.Data.(*model.Team)
|
||||
sendEmailChangeEmailAndForget(rusers[1].Email, rusers[0].Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL())
|
||||
sendEmailChangeEmailAndForget(c, rusers[1].Email, rusers[0].Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL())
|
||||
|
||||
if utils.Cfg.EmailSettings.RequireEmailVerification {
|
||||
SendEmailChangeVerifyEmailAndForget(rusers[0].Id, rusers[0].Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
|
||||
SendEmailChangeVerifyEmailAndForget(c, rusers[0].Id, rusers[0].Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1224,7 +1234,7 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
l4g.Error(tresult.Err.Message)
|
||||
} else {
|
||||
team := tresult.Data.(*model.Team)
|
||||
sendPasswordChangeEmailAndForget(user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), c.T("api.user.update_password.menu"))
|
||||
sendPasswordChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), c.T("api.user.update_password.menu"))
|
||||
}
|
||||
|
||||
data := make(map[string]string)
|
||||
@@ -1530,11 +1540,15 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
link := fmt.Sprintf("%s/reset_password?d=%s&h=%s", c.GetTeamURLFromTeam(team), url.QueryEscape(data), url.QueryEscape(hash))
|
||||
|
||||
subjectPage := NewServerTemplatePage("reset_subject")
|
||||
subjectPage.Props["SiteURL"] = c.GetSiteURL()
|
||||
bodyPage := NewServerTemplatePage("reset_body")
|
||||
subjectPage := NewServerTemplatePage("reset_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.reset_subject")
|
||||
|
||||
bodyPage := NewServerTemplatePage("reset_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = c.GetSiteURL()
|
||||
bodyPage.Props["Title"] = c.T("api.templates.reset_body.title")
|
||||
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.reset_body.info"))
|
||||
bodyPage.Props["ResetUrl"] = link
|
||||
bodyPage.Props["Button"] = c.T("api.templates.reset_body.button")
|
||||
|
||||
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||
c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message)
|
||||
@@ -1636,23 +1650,24 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.LogAuditWithUserId(userId, "success")
|
||||
}
|
||||
|
||||
sendPasswordChangeEmailAndForget(user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), "using a reset password link")
|
||||
sendPasswordChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), c.T("api.user.reset_password.method"))
|
||||
|
||||
props["new_password"] = ""
|
||||
w.Write([]byte(model.MapToJson(props)))
|
||||
}
|
||||
|
||||
func sendPasswordChangeEmailAndForget(email, teamDisplayName, teamURL, siteURL, method string) {
|
||||
func sendPasswordChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) {
|
||||
go func() {
|
||||
|
||||
subjectPage := NewServerTemplatePage("password_change_subject")
|
||||
subjectPage.Props["SiteURL"] = siteURL
|
||||
subjectPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage := NewServerTemplatePage("password_change_body")
|
||||
subjectPage := NewServerTemplatePage("password_change_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.password_change_subject",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
|
||||
|
||||
bodyPage := NewServerTemplatePage("password_change_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = siteURL
|
||||
bodyPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage.Props["TeamURL"] = teamURL
|
||||
bodyPage.Props["Method"] = method
|
||||
bodyPage.Props["Title"] = c.T("api.templates.password_change_body.title")
|
||||
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.password_change_body.info",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName, "TeamURL": teamURL, "Method": method}))
|
||||
|
||||
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||
l4g.Error(utils.T("api.user.send_password_change_email_and_forget.error"), err)
|
||||
@@ -1661,17 +1676,18 @@ func sendPasswordChangeEmailAndForget(email, teamDisplayName, teamURL, siteURL,
|
||||
}()
|
||||
}
|
||||
|
||||
func sendEmailChangeEmailAndForget(oldEmail, newEmail, teamDisplayName, teamURL, siteURL string) {
|
||||
func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, teamDisplayName, teamURL, siteURL string) {
|
||||
go func() {
|
||||
|
||||
subjectPage := NewServerTemplatePage("email_change_subject")
|
||||
subjectPage.Props["SiteURL"] = siteURL
|
||||
subjectPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage := NewServerTemplatePage("email_change_body")
|
||||
subjectPage := NewServerTemplatePage("email_change_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.email_change_body",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName})
|
||||
|
||||
bodyPage := NewServerTemplatePage("email_change_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = siteURL
|
||||
bodyPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage.Props["TeamURL"] = teamURL
|
||||
bodyPage.Props["NewEmail"] = newEmail
|
||||
bodyPage.Props["Title"] = c.T("api.templates.email_change_body.title")
|
||||
bodyPage.Props["Info"] = c.T("api.templates.email_change_body.info",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName, "NewEmail": newEmail})
|
||||
|
||||
if err := utils.SendMail(oldEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||
l4g.Error(utils.T("api.user.send_email_change_email_and_forget.error"), err)
|
||||
@@ -1680,18 +1696,22 @@ func sendEmailChangeEmailAndForget(oldEmail, newEmail, teamDisplayName, teamURL,
|
||||
}()
|
||||
}
|
||||
|
||||
func SendEmailChangeVerifyEmailAndForget(userId, newUserEmail, teamName, teamDisplayName, siteURL, teamURL string) {
|
||||
func SendEmailChangeVerifyEmailAndForget(c *Context, userId, newUserEmail, teamName, teamDisplayName, siteURL, teamURL string) {
|
||||
go func() {
|
||||
|
||||
link := fmt.Sprintf("%s/verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, newUserEmail)
|
||||
|
||||
subjectPage := NewServerTemplatePage("email_change_verify_subject")
|
||||
subjectPage.Props["SiteURL"] = siteURL
|
||||
subjectPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage := NewServerTemplatePage("email_change_verify_body")
|
||||
subjectPage := NewServerTemplatePage("email_change_verify_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.email_change_verify_subject",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName})
|
||||
|
||||
bodyPage := NewServerTemplatePage("email_change_verify_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = siteURL
|
||||
bodyPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage.Props["Title"] = c.T("api.templates.email_change_verify_body.title")
|
||||
bodyPage.Props["Info"] = c.T("api.templates.email_change_verify_body.info",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName})
|
||||
bodyPage.Props["VerifyUrl"] = link
|
||||
bodyPage.Props["VerifyButton"] = c.T("api.templates.email_change_verify_body.button")
|
||||
|
||||
if err := utils.SendMail(newUserEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||
l4g.Error(utils.T("api.user.send_email_change_verify_email_and_forget.error"), err)
|
||||
@@ -2032,7 +2052,7 @@ func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request,
|
||||
return
|
||||
}
|
||||
|
||||
sendSignInChangeEmailAndForget(user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), strings.Title(service)+" SSO")
|
||||
sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), strings.Title(service)+" SSO")
|
||||
}
|
||||
|
||||
func switchToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -2089,7 +2109,7 @@ func switchToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
sendSignInChangeEmailAndForget(user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), "email and password")
|
||||
sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email"))
|
||||
|
||||
RevokeAllSession(c, c.Session.UserId)
|
||||
if c.Err != nil {
|
||||
@@ -2103,17 +2123,18 @@ func switchToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(model.MapToJson(m)))
|
||||
}
|
||||
|
||||
func sendSignInChangeEmailAndForget(email, teamDisplayName, teamURL, siteURL, method string) {
|
||||
func sendSignInChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) {
|
||||
go func() {
|
||||
|
||||
subjectPage := NewServerTemplatePage("signin_change_subject")
|
||||
subjectPage.Props["SiteURL"] = siteURL
|
||||
subjectPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage := NewServerTemplatePage("signin_change_body")
|
||||
subjectPage := NewServerTemplatePage("signin_change_subject", c.Locale)
|
||||
subjectPage.Props["Subject"] = c.T("api.templates.singin_change_email.subject",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
|
||||
|
||||
bodyPage := NewServerTemplatePage("signin_change_body", c.Locale)
|
||||
bodyPage.Props["SiteURL"] = siteURL
|
||||
bodyPage.Props["TeamDisplayName"] = teamDisplayName
|
||||
bodyPage.Props["TeamURL"] = teamURL
|
||||
bodyPage.Props["Method"] = method
|
||||
bodyPage.Props["Title"] = c.T("api.templates.signin_change_email.body.title")
|
||||
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.singin_change_email.body.info",
|
||||
map[string]interface{}{"TeamDisplayName": teamDisplayName, "TeamURL": teamURL, "Method": method}))
|
||||
|
||||
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||
l4g.Error(utils.T("api.user.send_sign_in_change_email_and_forget.error"), err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -33,11 +34,11 @@ func NewWebConn(ws *websocket.Conn, teamId string, userId string, sessionId stri
|
||||
pchan := Srv.Store.User().UpdateLastPingAt(userId, model.GetMillis())
|
||||
|
||||
if result := <-achan; result.Err != nil {
|
||||
l4g.Error("Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v", userId, sessionId, result.Err)
|
||||
l4g.Error(utils.T("api.web_conn.new_web_conn.last_activity.error"), userId, sessionId, result.Err)
|
||||
}
|
||||
|
||||
if result := <-pchan; result.Err != nil {
|
||||
l4g.Error("Failed to updated LastPingAt for user_id=%v, err=%v", userId, result.Err)
|
||||
l4g.Error(utils.T("api.web_conn.new_web_conn.last_ping.error"), userId, result.Err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -56,7 +57,7 @@ func (c *WebConn) readPump() {
|
||||
|
||||
go func() {
|
||||
if result := <-Srv.Store.User().UpdateLastPingAt(c.UserId, model.GetMillis()); result.Err != nil {
|
||||
l4g.Error("Failed to updated LastPingAt for user_id=%v, err=%v", c.UserId, result.Err)
|
||||
l4g.Error(utils.T("api.web_conn.new_web_conn.last_ping.error"), c.UserId, result.Err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package api
|
||||
import (
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
type Hub struct {
|
||||
@@ -86,7 +87,7 @@ func (h *Hub) Start() {
|
||||
nh.broadcast <- msg
|
||||
}
|
||||
case s := <-h.stop:
|
||||
l4g.Debug("stopping %v connections", s)
|
||||
l4g.Debug(utils.T("api.web_hub.start.stopping.debug"), s)
|
||||
for _, v := range h.teamHubs {
|
||||
v.Stop()
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func InitWebSocket(r *mux.Router) {
|
||||
l4g.Debug("Initializing web socket api routes")
|
||||
l4g.Debug(utils.T("api.web_socket.init.debug"))
|
||||
r.Handle("/websocket", ApiUserRequired(connect)).Methods("GET")
|
||||
hub.Start()
|
||||
}
|
||||
@@ -28,8 +29,8 @@ func connect(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
l4g.Error("websocket connect err: %v", err)
|
||||
c.Err = model.NewAppError("connect", "Failed to upgrade websocket connection", "")
|
||||
l4g.Error(utils.T("api.web_socket.connect.error"), err)
|
||||
c.Err = model.NewLocAppError("connect", "api.web_socket.connect.upgrade.app_error", nil, "")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package api
|
||||
import (
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
type TeamHub struct {
|
||||
@@ -65,7 +66,7 @@ func (h *TeamHub) Start() {
|
||||
case s := <-h.stop:
|
||||
if s {
|
||||
|
||||
l4g.Debug("team hub stopping for teamId=%v", h.teamId)
|
||||
l4g.Debug(utils.T("api.web_team_hun.start.debug"), h.teamId)
|
||||
|
||||
for webCon := range h.connections {
|
||||
webCon.WebSocket.Close()
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func InitWebhook(r *mux.Router) {
|
||||
l4g.Debug("Initializing webhook api routes")
|
||||
l4g.Debug(utils.T("api.webhook.init.debug"))
|
||||
|
||||
sr := r.PathPrefix("/hooks").Subrouter()
|
||||
sr.Handle("/incoming/create", ApiUserRequired(createIncomingHook)).Methods("POST")
|
||||
@@ -27,7 +27,7 @@ func InitWebhook(r *mux.Router) {
|
||||
|
||||
func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
|
||||
c.Err = model.NewAppError("createIncomingHook", "Incoming webhooks have been disabled by the system admin.", "")
|
||||
c.Err = model.NewLocAppError("createIncomingHook", "api.webhook.create_incoming.disabled.app_errror", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
|
||||
c.Err = model.NewAppError("deleteIncomingHook", "Incoming webhooks have been disabled by the system admin.", "")
|
||||
c.Err = model.NewLocAppError("deleteIncomingHook", "api.webhook.delete_incoming.disabled.app_errror", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
if c.Session.UserId != result.Data.(*model.IncomingWebhook).UserId && !c.IsTeamAdmin() {
|
||||
c.LogAudit("fail - inappropriate permissions")
|
||||
c.Err = model.NewAppError("deleteIncomingHook", "Inappropriate permissions to delete incoming webhook", "user_id="+c.Session.UserId)
|
||||
c.Err = model.NewLocAppError("deleteIncomingHook", "api.webhook.delete_incoming.permissions.app_errror", nil, "user_id="+c.Session.UserId)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
|
||||
c.Err = model.NewAppError("getIncomingHooks", "Incoming webhooks have been disabled by the system admin.", "")
|
||||
c.Err = model.NewLocAppError("getIncomingHooks", "api.webhook.get_incoming.disabled.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
@@ -151,7 +151,7 @@ func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
|
||||
c.Err = model.NewAppError("createOutgoingHook", "Outgoing webhooks have been disabled by the system admin.", "")
|
||||
c.Err = model.NewLocAppError("createOutgoingHook", "api.webhook.create_outgoing.disabled.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
} else if len(hook.TriggerWords) == 0 {
|
||||
c.Err = model.NewAppError("createOutgoingHook", "Either trigger_words or channel_id must be set", "")
|
||||
c.Err = model.NewLocAppError("createOutgoingHook", "api.webhook.create_outgoing.triggers.app_error", nil, "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
|
||||
c.Err = model.NewAppError("getOutgoingHooks", "Outgoing webhooks have been disabled by the system admin.", "")
|
||||
c.Err = model.NewLocAppError("getOutgoingHooks", "api.webhook.get_outgoing.disabled.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
@@ -239,7 +239,7 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
|
||||
c.Err = model.NewAppError("deleteOutgoingHook", "Outgoing webhooks have been disabled by the system admin.", "")
|
||||
c.Err = model.NewLocAppError("deleteOutgoingHook", "api.webhook.delete_outgoing.disabled.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
@@ -268,7 +268,7 @@ func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
if c.Session.UserId != result.Data.(*model.OutgoingWebhook).CreatorId && !c.IsTeamAdmin() {
|
||||
c.LogAudit("fail - inappropriate permissions")
|
||||
c.Err = model.NewAppError("deleteOutgoingHook", "Inappropriate permissions to delete outcoming webhook", "user_id="+c.Session.UserId)
|
||||
c.Err = model.NewLocAppError("deleteOutgoingHook", "api.webhook.delete_outgoing.permissions.app_error", nil, "user_id="+c.Session.UserId)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -284,7 +284,7 @@ func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
|
||||
c.Err = model.NewAppError("regenOutgoingHookToken", "Outgoing webhooks have been disabled by the system admin.", "")
|
||||
c.Err = model.NewLocAppError("regenOutgoingHookToken", "api.webhook.regen_outgoing_token.disabled.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
@@ -316,7 +316,7 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
|
||||
if c.Session.TeamId != hook.TeamId && c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() {
|
||||
c.LogAudit("fail - inappropriate permissions")
|
||||
c.Err = model.NewAppError("regenOutgoingHookToken", "Inappropriate permissions to regenerate outcoming webhook token", "user_id="+c.Session.UserId)
|
||||
c.Err = model.NewLocAppError("regenOutgoingHookToken", "api.webhook.regen_outgoing_token.permissions.app_error", nil, "user_id="+c.Session.UserId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
930
i18n/en.json
930
i18n/en.json
@@ -1,4 +1,52 @@
|
||||
[
|
||||
{
|
||||
"id": "April",
|
||||
"translation": "April"
|
||||
},
|
||||
{
|
||||
"id": "August",
|
||||
"translation": "August"
|
||||
},
|
||||
{
|
||||
"id": "December",
|
||||
"translation": "December"
|
||||
},
|
||||
{
|
||||
"id": "February",
|
||||
"translation": "February"
|
||||
},
|
||||
{
|
||||
"id": "January",
|
||||
"translation": "January"
|
||||
},
|
||||
{
|
||||
"id": "July",
|
||||
"translation": "July"
|
||||
},
|
||||
{
|
||||
"id": "June",
|
||||
"translation": "June"
|
||||
},
|
||||
{
|
||||
"id": "March",
|
||||
"translation": "March"
|
||||
},
|
||||
{
|
||||
"id": "May",
|
||||
"translation": "May"
|
||||
},
|
||||
{
|
||||
"id": "November",
|
||||
"translation": "November"
|
||||
},
|
||||
{
|
||||
"id": "October",
|
||||
"translation": "October"
|
||||
},
|
||||
{
|
||||
"id": "September",
|
||||
"translation": "September"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.file_read_error",
|
||||
"translation": "Error reading log file"
|
||||
@@ -749,7 +797,7 @@
|
||||
},
|
||||
{
|
||||
"id": "api.post.update_post.permissions_details.app_error",
|
||||
"translation": "Already delted id={{.PostId}}"
|
||||
"translation": "Already deleted id={{.PostId}}"
|
||||
},
|
||||
{
|
||||
"id": "api.post_get_post_by_id.get.app_error",
|
||||
@@ -991,6 +1039,198 @@
|
||||
"id": "api.team.update_team.permissions.app_error",
|
||||
"translation": "You do not have the appropriate permissions"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_body.info",
|
||||
"translation": "You email address for {{.TeamDisplayName}} has been changed to {{.NewEmail}}.<br>If you did not make this change, please contact the system administrator."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_body.title",
|
||||
"translation": "You updated your email"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_subject",
|
||||
"translation": "Your email address has changed for {{.TeamDisplayName}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_body.button",
|
||||
"translation": "Verify Email"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_body.info",
|
||||
"translation": "To finish updating your email address for {{.TeamDisplayName}}, please click the link below to confirm this is the right address."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_body.title",
|
||||
"translation": "You updated your email"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_subject",
|
||||
"translation": "Verify new email address for {{.TeamDisplayName}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_footer",
|
||||
"translation": "To change your notification preferences, log in to your team site and go to Account Settings > Notifications."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_info",
|
||||
"translation": "Any questions at all, mail us any time: <a href='mailto:{{.FeedbackEmail}}' style='text-decoration: none; color:#2389D7;'>{{.FeedbackEmail}}</a>.<br>Best wishes,<br>The {{.SiteName}} Team<br>"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.error.link",
|
||||
"translation": "Go back to team site"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.error.title",
|
||||
"translation": "{{ .SiteName }} needs your help:"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_body.found",
|
||||
"translation": "Your request to find teams associated with your email found the following:"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_body.not_found",
|
||||
"translation": "We could not find any teams for the given email."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_body.title",
|
||||
"translation": "Finding teams"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_subject",
|
||||
"translation": "Your {{ .SiteName }} Teams"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.button",
|
||||
"translation": "Join Team"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.extra_info",
|
||||
"translation": "Mattermost lets you share messages and files from your PC or phone, with instant search and archiving. After you’ve joined <strong>{{.TeamDisplayName}}</strong>, you can sign-in to your new team and access these features anytime from the web address:<br/><br/><a href='{{.TeamURL}}'>{{.TeamURL}}</a>"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.info",
|
||||
"translation": "The team {{.SenderStatus}} <strong>{{.SenderName}}</strong>, has invited you to join <strong>{{.TeamDisplayName}}</strong>."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.title",
|
||||
"translation": "You've been invited"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_subject",
|
||||
"translation": "{{ .SenderName }} invited you to join {{ .TeamDisplayName }} Team on {{.SiteName}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.password_change_body.info",
|
||||
"translation": "Your password has been updated for {{.TeamDisplayName}} on {{ .TeamURL }} by {{.Method}}.<br>If this change wasn't initiated by you, please contact your system administrator."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.password_change_body.title",
|
||||
"translation": "You updated your password"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.password_change_subject",
|
||||
"translation": "Your password has been updated for {{.TeamDisplayName}} on {{ .SiteName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.post_body.button",
|
||||
"translation": "Go To Channel"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.post_body.info",
|
||||
"translation": "CHANNEL: {{.ChannelName}}<br>{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.post_subject",
|
||||
"translation": "{{.SubjectText}} on {{.TeamDisplayName}} at {{.Month}} {{.Day}}, {{.Year}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_body.button",
|
||||
"translation": "Reset Password"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_body.info",
|
||||
"translation": "To change your password, click \"Reset Password\" below.<br>If you did not mean to reset your password, please ignore this email and your password will remain the same."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_body.title",
|
||||
"translation": "You requested a password reset"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_subject",
|
||||
"translation": "Reset your password"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signin_change_email.body.method_email",
|
||||
"translation": "email and password"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signin_change_email.body.title",
|
||||
"translation": "You updated your sign-in method"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_body.button",
|
||||
"translation": "Set up your team"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_body.info",
|
||||
"translation": "{{ .SiteName }} is one place for all your team communication, searchable and available anywhere.<br>You'll get more out of {{ .SiteName }} when your team is in constant communication--let's get them on board."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_body.title",
|
||||
"translation": "Thanks for creating a team!"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_subject",
|
||||
"translation": "{{ .SiteName }} Team Setup"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.singin_change_email.body.info",
|
||||
"translation": "You updated your sign-in method for {{.TeamDisplayName}} on {{ .TeamURL }} to {{.Method}}.<br>If this change wasn't initiated by you, please contact your system administrator."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.singin_change_email.subject",
|
||||
"translation": "You updated your sign-in method for {{.TeamDisplayName}} on {{ .SiteName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_body.button",
|
||||
"translation": "Verify Email"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_body.info",
|
||||
"translation": "Please verify your email address by clicking below."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_body.title",
|
||||
"translation": "You've joined the {{ .TeamDisplayName }} team"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_subject",
|
||||
"translation": "[{{ .TeamDisplayName }} {{ .SiteName }}] Email Verification"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.button",
|
||||
"translation": "Verify Email"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.info",
|
||||
"translation": "Please verify your email address by clicking below."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.info2",
|
||||
"translation": "You can sign-in to your new team from the web address:"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.info3",
|
||||
"translation": "Mattermost lets you share messages and files from your PC or phone, with instant search and archiving."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.title",
|
||||
"translation": "You've joined the {{ .TeamDisplayName }} team"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_subject",
|
||||
"translation": "You joined {{ .TeamDisplayName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.user.add_direct_channels_and_forget.failed.error",
|
||||
"translation": "Failed to add direct channel preferences for user user_id=%s, team_id=%s, err=%v"
|
||||
@@ -1191,6 +1431,10 @@
|
||||
"id": "api.user.reset_password.link_expired.app_error",
|
||||
"translation": "The reset link has expired"
|
||||
},
|
||||
{
|
||||
"id": "api.user.reset_password.method",
|
||||
"translation": "using a reset password link"
|
||||
},
|
||||
{
|
||||
"id": "api.user.reset_password.sso.app_error",
|
||||
"translation": "Cannot reset password for SSO accounts"
|
||||
@@ -1319,12 +1563,696 @@
|
||||
"id": "api.user.upload_profile_user.too_large.app_error",
|
||||
"translation": "Unable to upload profile image. File is too large."
|
||||
},
|
||||
{
|
||||
"id": "api.web_conn.new_web_conn.last_activity.error",
|
||||
"translation": "Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "api.web_conn.new_web_conn.last_ping.error",
|
||||
"translation": "Failed to update LastPingAt for user_id=%v, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "api.web_hub.start.stopping.debug",
|
||||
"translation": "stopping %v connections"
|
||||
},
|
||||
{
|
||||
"id": "api.web_socket.connect.error",
|
||||
"translation": "websocket connect err: %v"
|
||||
},
|
||||
{
|
||||
"id": "api.web_socket.connect.upgrade.app_error",
|
||||
"translation": "Failed to upgrade websocket connection"
|
||||
},
|
||||
{
|
||||
"id": "api.web_socket.init.debug",
|
||||
"translation": "Initializing web socket api routes"
|
||||
},
|
||||
{
|
||||
"id": "api.web_team_hun.start.debug",
|
||||
"translation": "team hub stopping for teamId=%v"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.create_incoming.disabled.app_errror",
|
||||
"translation": "Incoming webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.create_outgoing.disabled.app_error",
|
||||
"translation": "Outgoing webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.create_outgoing.triggers.app_error",
|
||||
"translation": "Either trigger_words or channel_id must be set"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_incoming.disabled.app_errror",
|
||||
"translation": "Incoming webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_incoming.permissions.app_errror",
|
||||
"translation": "Inappropriate permissions to delete incoming webhook"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_outgoing.disabled.app_error",
|
||||
"translation": "Outgoing webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_outgoing.permissions.app_error",
|
||||
"translation": "Inappropriate permissions to delete outcoming webhook"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.get_incoming.disabled.app_error",
|
||||
"translation": "Incoming webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.get_outgoing.disabled.app_error",
|
||||
"translation": "Outgoing webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.init.debug",
|
||||
"translation": "Initializing webhook api routes"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.regen_outgoing_token.disabled.app_error",
|
||||
"translation": "Outgoing webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.regen_outgoing_token.permissions.app_error",
|
||||
"translation": "Inappropriate permissions to regenerate outcoming webhook token"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.get_channel_id.no_found.debug",
|
||||
"translation": "Could not find channel: %v, %v possibilites searched"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.get_channel_id.unable.debug",
|
||||
"translation": "Unable to get channels"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.create.info",
|
||||
"translation": "Creating user and team"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.parse.app_error",
|
||||
"translation": "Unable to parse URL"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.setup.info",
|
||||
"translation": "Setting up for manual test..."
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.uid.debug",
|
||||
"translation": "No uid in url"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.test_autolink.info",
|
||||
"translation": "Manual Auto Link Test"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.test_autolink.unable.app_error",
|
||||
"translation": "Unable to get channels"
|
||||
},
|
||||
{
|
||||
"id": "mattermost.current_version",
|
||||
"translation": "Current version is %v (%v/%v/%v)"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.access_token.app_error",
|
||||
"translation": "Invalid access token"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.auth_code.app_error",
|
||||
"translation": "Invalid auth code"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.redirect_uri.app_error",
|
||||
"translation": "Invalid redirect uri"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.refresh_token.app_error",
|
||||
"translation": "Invalid refresh token"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.auth_code.app_error",
|
||||
"translation": "Invalid authorization code"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.client_id.app_error",
|
||||
"translation": "Invalid client id"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.expires.app_error",
|
||||
"translation": "Expires in must be set"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.redirect_uri.app_error",
|
||||
"translation": "Invalid redirect uri"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.scope.app_error",
|
||||
"translation": "Invalid scope"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.state.app_error",
|
||||
"translation": "Invalid state"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.user_id.app_error",
|
||||
"translation": "Invalid user id"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.2_or_more.app_error",
|
||||
"translation": "Name must be 2 or more lowercase alphanumeric characters"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.creator_id.app_error",
|
||||
"translation": "Invalid creator id"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.display_name.app_error",
|
||||
"translation": "Invalid display name"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.header.app_error",
|
||||
"translation": "Invalid header"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.id.app_error",
|
||||
"translation": "Invalid Id"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.name.app_error",
|
||||
"translation": "Invalid name"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.purpose.app_error",
|
||||
"translation": "Invalid purpose"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.type.app_error",
|
||||
"translation": "Invalid type"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.channel_id.app_error",
|
||||
"translation": "Invalid channel id"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.notify_level.app_error",
|
||||
"translation": "Invalid notify level"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.role.app_error",
|
||||
"translation": "Invalid role"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.unread_level.app_error",
|
||||
"translation": "Invalid mark unread level"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.user_id.app_error",
|
||||
"translation": "Invalid user id"
|
||||
},
|
||||
{
|
||||
"id": "model.client.connecting.app_error",
|
||||
"translation": "We encountered an error while connecting to the server"
|
||||
},
|
||||
{
|
||||
"id": "model.client.login.app_error",
|
||||
"translation": "Authentication tokens didn't match"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.email_reset_salt.app_error",
|
||||
"translation": "Invalid password reset salt for email settings. Must be 32 chars or more."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.email_salt.app_error",
|
||||
"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'"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.encrypt_sql.app_error",
|
||||
"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'"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_preview_height.app_error",
|
||||
"translation": "Invalid preview height for file settings. Must be a zero or positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_preview_width.app_error",
|
||||
"translation": "Invalid preview width for file settings. Must be a positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_profile_height.app_error",
|
||||
"translation": "Invalid profile height for file settings. Must be a positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_profile_width.app_error",
|
||||
"translation": "Invalid profile width for file settings. Must be a positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_salt.app_error",
|
||||
"translation": "Invalid public link salt for file settings. Must be 32 chars or more."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_thumb_height.app_error",
|
||||
"translation": "Invalid thumbnail height for file settings. Must be a positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_thumb_width.app_error",
|
||||
"translation": "Invalid thumbnail width for file settings. Must be a positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.listen_address.app_error",
|
||||
"translation": "Invalid listen address for service settings Must be set."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.login_attempts.app_error",
|
||||
"translation": "Invalid maximum login attempts for service 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."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.rate_mem.app_error",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_data_src.app_error",
|
||||
"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'"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_idle.app_error",
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"id": "model.file_info.get.gif.app_error",
|
||||
"translation": "Could not decode gif."
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.channel_id.app_error",
|
||||
"translation": "Invalid channel id"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.id.app_error",
|
||||
"translation": "Invalid Id"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.team_id.app_error",
|
||||
"translation": "Invalid team id"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.update_at.app_error",
|
||||
"translation": "Update at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.user_id.app_error",
|
||||
"translation": "Invalid user id"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.app_id.app_error",
|
||||
"translation": "Invalid app id"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.callback.app_error",
|
||||
"translation": "Invalid callback urls"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.client_secret.app_error",
|
||||
"translation": "Invalid client secret"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.creator_id.app_error",
|
||||
"translation": "Invalid creator id"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.description.app_error",
|
||||
"translation": "Invalid description"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.homepage.app_error",
|
||||
"translation": "Invalid homepage"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.name.app_error",
|
||||
"translation": "Invalid name"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.callback.app_error",
|
||||
"translation": "Invalid callback urls"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.channel_id.app_error",
|
||||
"translation": "Invalid channel id"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.id.app_error",
|
||||
"translation": "Invalid Id"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.team_id.app_error",
|
||||
"translation": "Invalid team id"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.token.app_error",
|
||||
"translation": "Invalid token"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.url.app_error",
|
||||
"translation": "Invalid callback URLs. Each must be a valid URL and start with http:// or https://"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.user_id.app_error",
|
||||
"translation": "Invalid user id"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.words.app_error",
|
||||
"translation": "Invalid trigger words"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.channel_id.app_error",
|
||||
"translation": "Invalid channel id"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.filenames.app_error",
|
||||
"translation": "Invalid filenames"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.hashtags.app_error",
|
||||
"translation": "Invalid hashtags"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.id.app_error",
|
||||
"translation": "Invalid Id"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.msg.app_error",
|
||||
"translation": "Invalid message"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.original_id.app_error",
|
||||
"translation": "Invalid original id"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.parent_id.app_error",
|
||||
"translation": "Invalid parent id"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.props.app_error",
|
||||
"translation": "Invalid props"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.root_id.app_error",
|
||||
"translation": "Invalid root id"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.root_parent.app_error",
|
||||
"translation": "Invalid root id must be set if parent id set"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.type.app_error",
|
||||
"translation": "Invalid type"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.user_id.app_error",
|
||||
"translation": "Invalid user id"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.category.app_error",
|
||||
"translation": "Invalid category"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.id.app_error",
|
||||
"translation": "Invalid user id"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.name.app_error",
|
||||
"translation": "Invalid name"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.value.app_error",
|
||||
"translation": "Value is too long"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.characters.app_error",
|
||||
"translation": "Name must be 4 or more lowercase alphanumeric characters"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.company.app_error",
|
||||
"translation": "Invalid company name"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.domains.app_error",
|
||||
"translation": "Invalid allowed domains"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.email.app_error",
|
||||
"translation": "Invalid email"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.id.app_error",
|
||||
"translation": "Invalid Id"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.name.app_error",
|
||||
"translation": "Invalid name"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.reserved.app_error",
|
||||
"translation": "This URL is unavailable. Please try another."
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.type.app_error",
|
||||
"translation": "Invalid type"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.url.app_error",
|
||||
"translation": "Invalid URL Identifier"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.auth_data.app_error",
|
||||
"translation": "Invalid auth data"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.auth_data_pwd.app_error",
|
||||
"translation": "Invalid user, password and auth data cannot both be set"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.auth_data_type.app_error",
|
||||
"translation": "Invalid user, auth data must be set with auth type"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.email.app_error",
|
||||
"translation": "Invalid email"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.first_name.app_error",
|
||||
"translation": "Invalid first name"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.id.app_error",
|
||||
"translation": "Invalid user id"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.last_name.app_error",
|
||||
"translation": "Invalid last name"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.nickname.app_error",
|
||||
"translation": "Invalid nickname"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.pwd.app_error",
|
||||
"translation": "Invalid password"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.team_id.app_error",
|
||||
"translation": "Invalid team id"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.theme.app_error",
|
||||
"translation": "Invalid theme"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.update_at.app_error",
|
||||
"translation": "Update at must be a valid time"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.username.app_error",
|
||||
"translation": "Invalid username"
|
||||
},
|
||||
{
|
||||
"id": "model.utils.decode_json.app_error",
|
||||
"translation": "could not decode"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.decoding.panic",
|
||||
"translation": "Error decoding config file={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.getting.panic",
|
||||
"translation": "Error getting config info file={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.opening.panic",
|
||||
"translation": "Error opening config file={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.validating.panic",
|
||||
"translation": "Error validating config file={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.save_config.saving.app_error",
|
||||
"translation": "An error occurred while saving the file to {{.Filename}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.i18n.loaded",
|
||||
"translation": "Loaded system translations for '%v' from '%v'"
|
||||
},
|
||||
{
|
||||
"id": "utils.iru.with_evict",
|
||||
"translation": "Must provide a positive size"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.load_license.invalid.warn",
|
||||
"translation": "No valid enterprise license found"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.load_license.open_find.warn",
|
||||
"translation": "Unable to open/find license file"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.remove_license.unable.error",
|
||||
"translation": "Unable to remove license file, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.decode.error",
|
||||
"translation": "Encountered error decoding license, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.invalid.error",
|
||||
"translation": "Invalid signature, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.not_long.error",
|
||||
"translation": "Signed license not long enough"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.signing.error",
|
||||
"translation": "Encountered error signing license, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.connect_smtp.open.app_error",
|
||||
"translation": "Failed to open connection"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.connect_smtp.open_tls.app_error",
|
||||
"translation": "Failed to open TLS connection"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.new_client.auth.app_error",
|
||||
"translation": "Failed to authenticate on SMTP server"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.new_client.open.error",
|
||||
"translation": "Failed to open a connection to SMTP server %v"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.close.app_error",
|
||||
"translation": "Failed to close connection to SMTP server"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.from_address.app_error",
|
||||
"translation": "Failed to add from email address"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.msg.app_error",
|
||||
"translation": "Failed to write email message"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.msg_data.app_error",
|
||||
"translation": "Failed to add email messsage data"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.sending.debug",
|
||||
"translation": "sending mail to %v with subject of '%v'"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.to_address.app_error",
|
||||
"translation": "Failed to add to email address"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.test.configured.error",
|
||||
"translation": "SMTP server settings do not appear to be configured properly err=%v details=%v"
|
||||
}
|
||||
]
|
||||
930
i18n/es.json
930
i18n/es.json
@@ -1,4 +1,52 @@
|
||||
[
|
||||
{
|
||||
"id": "April",
|
||||
"translation": "Abril"
|
||||
},
|
||||
{
|
||||
"id": "August",
|
||||
"translation": "Agosto"
|
||||
},
|
||||
{
|
||||
"id": "December",
|
||||
"translation": "Diciembre"
|
||||
},
|
||||
{
|
||||
"id": "February",
|
||||
"translation": "Febrero"
|
||||
},
|
||||
{
|
||||
"id": "January",
|
||||
"translation": "Enero"
|
||||
},
|
||||
{
|
||||
"id": "July",
|
||||
"translation": "Julio"
|
||||
},
|
||||
{
|
||||
"id": "June",
|
||||
"translation": "Junio"
|
||||
},
|
||||
{
|
||||
"id": "March",
|
||||
"translation": "Marzo"
|
||||
},
|
||||
{
|
||||
"id": "May",
|
||||
"translation": "Mayo"
|
||||
},
|
||||
{
|
||||
"id": "November",
|
||||
"translation": "Noviembre"
|
||||
},
|
||||
{
|
||||
"id": "October",
|
||||
"translation": "Octubre"
|
||||
},
|
||||
{
|
||||
"id": "September",
|
||||
"translation": "Septiembre"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.file_read_error",
|
||||
"translation": "Error leyendo el archivo de registro"
|
||||
@@ -749,7 +797,7 @@
|
||||
},
|
||||
{
|
||||
"id": "api.post.update_post.permissions_details.app_error",
|
||||
"translation": "Already delted id={{.PostId}}"
|
||||
"translation": "Ya fué elminado el id={{.PostId}}"
|
||||
},
|
||||
{
|
||||
"id": "api.post_get_post_by_id.get.app_error",
|
||||
@@ -991,6 +1039,198 @@
|
||||
"id": "api.team.update_team.permissions.app_error",
|
||||
"translation": "No tienes los permisos apropiados"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_body.info",
|
||||
"translation": "Tu dirección de correo electrónico para {{.TeamDisplayName}} ha sido cambiada por {{.NewEmail}}.<br>Si este cambio no fue realizado por ti, por favor contacta un administrador de sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_body.title",
|
||||
"translation": "Haz actualizado tu correo electrónico"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_subject",
|
||||
"translation": "Tu dirección de correo para {{.TeamDisplayName}} ha cambiado"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_body.button",
|
||||
"translation": "Confirmar Correo"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_body.info",
|
||||
"translation": "Para terminar de actualizar tu dirección de correo para {{.TeamDisplayName}}, por favor pincha en botón de abajo para confirmar que está es la dirección correcta."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_body.title",
|
||||
"translation": "Haz actualizado tu correo electrónico"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_change_verify_subject",
|
||||
"translation": "Verificación de la nueva dirección de correo electrónico para {{.TeamDisplayName}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_footer",
|
||||
"translation": "Para cambiar tus preferencias de notificaciones, inicia sesión en tu equipo y dirigete a Configurar Cuenta > Notificaciones."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.email_info",
|
||||
"translation": "Si tienes alguna pregunta, escribenos en cualquier momento a: <a href=\"mailto:{{.FeedbackEmail}}\" style=\"text-decoration: none; color:#2389D7;\">{{.FeedbackEmail}}</a>.<br>Los mejores deseos,<br>El Equipo {{.SiteName}}<br>"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.error.link",
|
||||
"translation": "Volver al sitio del equipo"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.error.title",
|
||||
"translation": "Necesitamos de tu ayuda en {{ .SiteName }}:"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_body.found",
|
||||
"translation": "Haz solicitado encontrar los equipos a los que tienes asociado tu correo electrónico y encontramos los siguientes:"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_body.not_found",
|
||||
"translation": "No hemos podido encontrar ningún equipo asociado al correo electrónico suministrado."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_body.title",
|
||||
"translation": "Tus Equipos"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.find_teams_subject",
|
||||
"translation": "Tus equipos de {{ .SiteName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.button",
|
||||
"translation": "Unirme al Equipo"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.extra_info",
|
||||
"translation": "Mattermost te permite compartir mensajes y archivos de tu Computador o teléfono, incluyendo busquedas instantáneas. Una vez que te hayas unido a <strong>{{.TeamDisplayName}}</strong>, Podrás iniciar sesión en tu nuevo equipo y utilizar todas las características en cualquier momento desde la dirección web:<br/><br/><a href=\"{{.TeamURL}}\">{{.TeamURL}}</a>"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.info",
|
||||
"translation": "<strong>{{.SenderName}}</strong> {{.SenderStatus}} del equipo <strong>{{.TeamDisplayName}}</strong>, te ha invitado a unirte."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_body.title",
|
||||
"translation": "Haz sido invitado"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.invite_subject",
|
||||
"translation": "{{ .SenderName }} te ha invitado a unirte al equipo {{ .TeamDisplayName }} en {{.SiteName}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.password_change_body.info",
|
||||
"translation": "Tu contraseña ha sido cambiada para {{.TeamDisplayName}} en {{ .TeamURL }} por el método de {{.Method}}.<br>Si este cambio no fue realizado por ti, por favor contacta a un administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.password_change_body.title",
|
||||
"translation": "Haz cambiado tu contraseña"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.password_change_subject",
|
||||
"translation": "Tu contraseña ha sido cambiada para {{.TeamDisplayName}} en {{ .SiteName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.post_body.button",
|
||||
"translation": "Ir al Canal"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.post_body.info",
|
||||
"translation": "Canal: {{.ChannelName}}<br>{{.SenderName}} - {{.Day}} {{.Month}}, {{.Hour}}:{{.Minute}} {{.TimeZone}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.post_subject",
|
||||
"translation": "{{.SubjectText}} en {{.TeamDisplayName}} el {{.Day}} {{.Month}}, {{.Year}}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_body.button",
|
||||
"translation": "Restablecer Contraseña"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_body.info",
|
||||
"translation": "Para cambiar tu contraseña, pincha el botón \"Restablecer Contraseña\" que se encuentra abajo.<br>Si no fue tu intención restablecer tu contraseña, por favor ignora este correo y tu contraseña seguirá siendo la misma."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_body.title",
|
||||
"translation": "Haz solicitado restablecer tu contraseña"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.reset_subject",
|
||||
"translation": "Restablece tu contraseña"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signin_change_email.body.method_email",
|
||||
"translation": "correo electrónico y contraseña"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signin_change_email.body.title",
|
||||
"translation": "Haz actualizado el método con el que inicias sesión"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_body.button",
|
||||
"translation": "Configura tu equipo"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_body.info",
|
||||
"translation": "{{ .SiteName }} es el lugar para todas las comunicaciones de tu equipo, con capacidades de búsqueda y disponible desde cualquier parte.<br>Podrás aprovechar al máximo {{ .SiteName }} cuando tu equipo esté en constante comunicación--Traigamoslos a bordo."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_body.title",
|
||||
"translation": "¡Gracias por haber creado un equipo!"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.signup_team_subject",
|
||||
"translation": "Configuración del equipo en {{ .SiteName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.singin_change_email.body.info",
|
||||
"translation": "Haz actualizado el método con el que inicias sesión en {{.TeamURL}} para el equipo {{.TeamDisplayName}} por {{.Method}}.<br>Si este cambio no fue realizado por ti, por favor contacta a un administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.singin_change_email.subject",
|
||||
"translation": "Cambio del método de inicio de sesión para {{.TeamDisplayName}} en {{ .SiteName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_body.button",
|
||||
"translation": "Confirmar Correo"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_body.info",
|
||||
"translation": "Por favor verifica tu correo electrónico al pinchar el botón de abajo."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_body.title",
|
||||
"translation": "Te haz unido al equipo {{ .TeamDisplayName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.verify_subject",
|
||||
"translation": "[{{ .TeamDisplayName }} {{ .SiteName }}] Correo de Verificación"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.button",
|
||||
"translation": "Confirmar Correo"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.info",
|
||||
"translation": "Por favor verifica tu correo electrónico al pinchar el botón de abajo."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.info2",
|
||||
"translation": "Puedes iniciar sesión en tu nuevo equipo desde la dirección web:"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.info3",
|
||||
"translation": "Mattermost te permite compartir mensajes y archivos desde un computador o teléfono desde donde te encuentres."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_body.title",
|
||||
"translation": "Te haz unido al equipo {{ .TeamDisplayName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.welcome_subject",
|
||||
"translation": "Te haz unido a {{ .TeamDisplayName }}"
|
||||
},
|
||||
{
|
||||
"id": "api.user.add_direct_channels_and_forget.failed.error",
|
||||
"translation": "Falla al agragar las preferencias del canal directo para el usuario user_id=%s, team_id=%s, err=%v"
|
||||
@@ -1191,6 +1431,10 @@
|
||||
"id": "api.user.reset_password.link_expired.app_error",
|
||||
"translation": "El enlace para restablecer la contraseña ha expirado"
|
||||
},
|
||||
{
|
||||
"id": "api.user.reset_password.method",
|
||||
"translation": "utilizando el enlace para restablecer contraseña"
|
||||
},
|
||||
{
|
||||
"id": "api.user.reset_password.sso.app_error",
|
||||
"translation": "No se puede restablecer la contraseña para cuentas SSO"
|
||||
@@ -1319,12 +1563,696 @@
|
||||
"id": "api.user.upload_profile_user.too_large.app_error",
|
||||
"translation": "No se pudo actualizar la imagen del perfil. El archivo es muy grande."
|
||||
},
|
||||
{
|
||||
"id": "api.web_conn.new_web_conn.last_activity.error",
|
||||
"translation": "Falla al actualizar LastActivityAt para user_id=%v and session_id=%v, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "api.web_conn.new_web_conn.last_ping.error",
|
||||
"translation": "Falla al actualizar LastPingAt para el user_id=%v, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "api.web_hub.start.stopping.debug",
|
||||
"translation": "deteniendo todas las conexiones"
|
||||
},
|
||||
{
|
||||
"id": "api.web_socket.connect.error",
|
||||
"translation": "conexión al websocket err: %v"
|
||||
},
|
||||
{
|
||||
"id": "api.web_socket.connect.upgrade.app_error",
|
||||
"translation": "Falla al actualizar la conexión del websocket"
|
||||
},
|
||||
{
|
||||
"id": "api.web_socket.init.debug",
|
||||
"translation": "Inicializando rutas del API para los web socket"
|
||||
},
|
||||
{
|
||||
"id": "api.web_team_hun.start.debug",
|
||||
"translation": "deteniendo el hub de equipo para teamId=%v"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.create_incoming.disabled.app_errror",
|
||||
"translation": "Webhooks entrantes han sido deshabilitados por el administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.create_outgoing.disabled.app_error",
|
||||
"translation": "Webhooks de Salida han sido deshabilitados por el administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.create_outgoing.triggers.app_error",
|
||||
"translation": "Debe establecerse palabras gatilladoras o un channel_id"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_incoming.disabled.app_errror",
|
||||
"translation": "Webhooks entrantes han sido deshabilitados por el administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_incoming.permissions.app_errror",
|
||||
"translation": "Permisos inapropiados para eliminar un webhook entrante"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_outgoing.disabled.app_error",
|
||||
"translation": "Webhooks de Salida han sido deshabilitados por el administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.delete_outgoing.permissions.app_error",
|
||||
"translation": "Permisos inapropiados para eliminar el webhook saliente"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.get_incoming.disabled.app_error",
|
||||
"translation": "Webhooks entrantes han sido deshabilitados por el administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.get_outgoing.disabled.app_error",
|
||||
"translation": "Webhooks de Salida han sido deshabilitados por el administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.init.debug",
|
||||
"translation": "Inicializando rutas del API para los Webhooks"
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.regen_outgoing_token.disabled.app_error",
|
||||
"translation": "Webhooks de Salida han sido deshabilitados por el administrador del sistema."
|
||||
},
|
||||
{
|
||||
"id": "api.webhook.regen_outgoing_token.permissions.app_error",
|
||||
"translation": "Permisos inapropiados para regenerar un token para el Webhook saliente"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.get_channel_id.no_found.debug",
|
||||
"translation": "No pudimos encontrar el canal: %v, búsqueda realizada con estas posibilidades %v"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.get_channel_id.unable.debug",
|
||||
"translation": "No se pudo obtener los canales"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.create.info",
|
||||
"translation": "Creando usuario y equipo"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.parse.app_error",
|
||||
"translation": "No se pudo analizar el URL"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.setup.info",
|
||||
"translation": "Configurando para pruebas manuales..."
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.manual_test.uid.debug",
|
||||
"translation": "No hay un uid en el url"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.test_autolink.info",
|
||||
"translation": "Prueba Manual de Enlaces Automáticos"
|
||||
},
|
||||
{
|
||||
"id": "manaultesting.test_autolink.unable.app_error",
|
||||
"translation": "No se pudo obtener los canales"
|
||||
},
|
||||
{
|
||||
"id": "mattermost.current_version",
|
||||
"translation": "La versión actual es %v (%v/%v/%v)"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.access_token.app_error",
|
||||
"translation": "Token de acceso inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.auth_code.app_error",
|
||||
"translation": "Código de autenticación inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.redirect_uri.app_error",
|
||||
"translation": "URI de redireccionamiento inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.access.is_valid.refresh_token.app_error",
|
||||
"translation": "Token de refrescamiento inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.auth_code.app_error",
|
||||
"translation": "Código de autorización inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.client_id.app_error",
|
||||
"translation": "Id de cliente inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.expires.app_error",
|
||||
"translation": "Se debe asignar el tiempo de Expiración"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.redirect_uri.app_error",
|
||||
"translation": "URI de redireccionamiento inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.scope.app_error",
|
||||
"translation": "Alcance inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.state.app_error",
|
||||
"translation": "Estado inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.authorize.is_valid.user_id.app_error",
|
||||
"translation": "Usuario id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.2_or_more.app_error",
|
||||
"translation": "Debe tener 2 o más caracteres alfanuméricos en minúscula"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.creator_id.app_error",
|
||||
"translation": "Id del creador inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.display_name.app_error",
|
||||
"translation": "Nombre a mostrar inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.header.app_error",
|
||||
"translation": "Encabezado inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.id.app_error",
|
||||
"translation": "Id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.name.app_error",
|
||||
"translation": "Nombre inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.purpose.app_error",
|
||||
"translation": "Propósito inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.type.app_error",
|
||||
"translation": "Tipo inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel.is_valid.update_at.app_error",
|
||||
"translation": "Update debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.channel_id.app_error",
|
||||
"translation": "Channel id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.notify_level.app_error",
|
||||
"translation": "Nivel de notificación inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.role.app_error",
|
||||
"translation": "Rol inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.unread_level.app_error",
|
||||
"translation": "Nivel de marca para no leidos inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.channel_member.is_valid.user_id.app_error",
|
||||
"translation": "User id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.client.connecting.app_error",
|
||||
"translation": "Encontramos un error mientras conectabamos al servidor"
|
||||
},
|
||||
{
|
||||
"id": "model.client.login.app_error",
|
||||
"translation": "Token de autenticación no coincidió"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.email_reset_salt.app_error",
|
||||
"translation": "Salt para restablecer contraseñas en la configuración de correos es inválido. Debe ser de 32 caracteres o más."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.email_salt.app_error",
|
||||
"translation": "Salt para crear invitaciones en la configuración de correos es inválido. Debe ser de 32 caracteres o más."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.email_security.app_error",
|
||||
"translation": "Configuración inválida de seguridad en la configuración de correos. Debe ser '', 'TLS', o 'STARTTLS'"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.encrypt_sql.app_error",
|
||||
"translation": "Llave de cifrado rest para las configuraciones de SQL inválida. Debe ser de 32 caracteres o más."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_driver.app_error",
|
||||
"translation": "Nombre de controlador para la configuración de archivos es inválido. Debe ser 'local' o 'amazons3'"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_preview_height.app_error",
|
||||
"translation": "La altura para la previsualización es inválido en la configuración de archivos. Debe ser cero o un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_preview_width.app_error",
|
||||
"translation": "El ancho para la previsualización es inválido en la configuración de archivos. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_profile_height.app_error",
|
||||
"translation": "La altura para la imagen de perfil es inválido en la configuración de archivos. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_profile_width.app_error",
|
||||
"translation": "El ancho para la imagen de perfil es inválido en la configuración de archivos. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_salt.app_error",
|
||||
"translation": "Salt para crear enlaces públicos en la configuración a archivos es inválido. Debe ser de 32 caracteres o más."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_thumb_height.app_error",
|
||||
"translation": "La altura para la imagen de miniatura es inválido en la configuración de archivos. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.file_thumb_width.app_error",
|
||||
"translation": "El ancho para la imagen de miniatura es inválido en la configuración de archivos. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.listen_address.app_error",
|
||||
"translation": "Dirección dónde se escuchará el servicio en la configuracón del servicio debe ser asignada."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.login_attempts.app_error",
|
||||
"translation": "Número inválido de máximos intentos de inició de sesión en la configuración del servicio. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.max_users.app_error",
|
||||
"translation": "Número inválido del máximo de usuarios por equipo en la configuración de equipo. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.rate_mem.app_error",
|
||||
"translation": "Tamaño del almacen de memoria inválido en la configuración de límites de velocidad. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.rate_sec.app_error",
|
||||
"translation": "Por segundo es inválido en la configuración de límites de velocidad. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_data_src.app_error",
|
||||
"translation": "Fuente de datos no válido para la configuración de SQL. Debe ser asignado."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_driver.app_error",
|
||||
"translation": "Nombre del controlador no válido para la configuración de SQL. Debe ser 'mysql' o 'postgres'"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_idle.app_error",
|
||||
"translation": "Inválido máxima de conexión inactiva para la configuración de SQL. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_max_conn.app_error",
|
||||
"translation": "Inválida cantidad de conexiones abiertas para la configuración de SQL. Debe ser un número positivo."
|
||||
},
|
||||
{
|
||||
"id": "model.file_info.get.gif.app_error",
|
||||
"translation": "No se pudo decodificar el gif."
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.channel_id.app_error",
|
||||
"translation": "Channel id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.id.app_error",
|
||||
"translation": "Id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.team_id.app_error",
|
||||
"translation": "Id del Equipo inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.update_at.app_error",
|
||||
"translation": "Update debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.incoming_hook.user_id.app_error",
|
||||
"translation": "Id del Usuario inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.app_id.app_error",
|
||||
"translation": "Id de la App inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.callback.app_error",
|
||||
"translation": "Callback urls inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.client_secret.app_error",
|
||||
"translation": "Llave secreta del Cliente no es válida"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.creator_id.app_error",
|
||||
"translation": "Id del credor no es válido"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.description.app_error",
|
||||
"translation": "Descripción inválida"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.homepage.app_error",
|
||||
"translation": "Página principal inválida"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.name.app_error",
|
||||
"translation": "Nombre inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.oauth.is_valid.update_at.app_error",
|
||||
"translation": "Update debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.callback.app_error",
|
||||
"translation": "Callback urls inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.channel_id.app_error",
|
||||
"translation": "Id del Canal inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.id.app_error",
|
||||
"translation": "Id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.team_id.app_error",
|
||||
"translation": "Id del Equipo inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.token.app_error",
|
||||
"translation": "Token inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.update_at.app_error",
|
||||
"translation": "Update debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.url.app_error",
|
||||
"translation": "Callback URLs inválido. Cada uno debe ser un URL válido y que comience con http:// o https://"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.user_id.app_error",
|
||||
"translation": "Id del Usuario inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.outgoing_hook.is_valid.words.app_error",
|
||||
"translation": "Palabras gatilladoras inválidas"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.channel_id.app_error",
|
||||
"translation": "Id del Canal inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.filenames.app_error",
|
||||
"translation": "Nombre de archivos no válidos"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.hashtags.app_error",
|
||||
"translation": "Hashtags inválidos"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.id.app_error",
|
||||
"translation": "Id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.msg.app_error",
|
||||
"translation": "Mensaje no es válido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.original_id.app_error",
|
||||
"translation": "Id Original inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.parent_id.app_error",
|
||||
"translation": "Id del padre no es válido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.props.app_error",
|
||||
"translation": "Props inválidos"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.root_id.app_error",
|
||||
"translation": "Id de la raíz no es válido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.root_parent.app_error",
|
||||
"translation": "Id de la raíz no es válido, debe ser asignado si el Id del padre fue asignado"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.type.app_error",
|
||||
"translation": "Tipo inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.update_at.app_error",
|
||||
"translation": "Update debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.post.is_valid.user_id.app_error",
|
||||
"translation": "Id del Usuario inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.category.app_error",
|
||||
"translation": "Categoría inválida"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.id.app_error",
|
||||
"translation": "Id del Usuario inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.name.app_error",
|
||||
"translation": "Nombre inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.preference.is_valid.value.app_error",
|
||||
"translation": "El valor es muy largo"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.characters.app_error",
|
||||
"translation": "Nombre tiene que ser de 4 o más caracteres alfanuméricos en minúsculas"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.company.app_error",
|
||||
"translation": "Inválido nombre de la empresa"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.domains.app_error",
|
||||
"translation": "Dominios permitidos no válidos"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.email.app_error",
|
||||
"translation": "Correo electrónico inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.id.app_error",
|
||||
"translation": "Id inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.name.app_error",
|
||||
"translation": "Nombre inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.reserved.app_error",
|
||||
"translation": "Este URL no está disponible. Por favor, prueba con otro."
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.type.app_error",
|
||||
"translation": "Tipo inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.update_at.app_error",
|
||||
"translation": "Update debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.team.is_valid.url.app_error",
|
||||
"translation": "Identificador del URL es inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.auth_data.app_error",
|
||||
"translation": "Data de auth es inválida"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.auth_data_pwd.app_error",
|
||||
"translation": "Usuario inválido, no pueden ser asignados auth data y la contraseña al mismo tiempo"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.auth_data_type.app_error",
|
||||
"translation": "Usuario inválido, auth data debe ser asignado con un tipo de auth"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.create_at.app_error",
|
||||
"translation": "Create debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.email.app_error",
|
||||
"translation": "Correo electrónico inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.first_name.app_error",
|
||||
"translation": "Nombre inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.id.app_error",
|
||||
"translation": "Id del Usuario inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.last_name.app_error",
|
||||
"translation": "Apellido no es válido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.nickname.app_error",
|
||||
"translation": "Sobrenombre no es válido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.pwd.app_error",
|
||||
"translation": "Contraseña inválida"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.team_id.app_error",
|
||||
"translation": "Id del Equipo inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.theme.app_error",
|
||||
"translation": "Tema inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.update_at.app_error",
|
||||
"translation": "Update debe ser un tiempo válido"
|
||||
},
|
||||
{
|
||||
"id": "model.user.is_valid.username.app_error",
|
||||
"translation": "Nombre de usuario inválido"
|
||||
},
|
||||
{
|
||||
"id": "model.utils.decode_json.app_error",
|
||||
"translation": "no se puede decodificar"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.decoding.panic",
|
||||
"translation": "Error decifrando la configuración del archivo={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.getting.panic",
|
||||
"translation": "Error obteniendo la iformación de configuración del archivo={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.opening.panic",
|
||||
"translation": "Error abriendo la configuración del archivo={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.load_config.validating.panic",
|
||||
"translation": "Error validando la configuración del archivo={{.Filename}}, err={{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.config.save_config.saving.app_error",
|
||||
"translation": "Ocurrió un error mientras se guardaba el archivo en {{.Filename}}"
|
||||
},
|
||||
{
|
||||
"id": "utils.i18n.loaded",
|
||||
"translation": "Cargada traducciones del sistema para '%v' desde '%v'"
|
||||
},
|
||||
{
|
||||
"id": "utils.iru.with_evict",
|
||||
"translation": "Debe proporcionar un tamaño positivo"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.load_license.invalid.warn",
|
||||
"translation": "No se encontró una licencia enterprise válida"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.load_license.open_find.warn",
|
||||
"translation": "No pudimos encontrar/abrir el achivo de licencia"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.remove_license.unable.error",
|
||||
"translation": "No se pudo remover el archivo de la licencia, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.decode.error",
|
||||
"translation": "Encontramos un error decodificando la licencia, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.invalid.error",
|
||||
"translation": "Firma inválida, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.not_long.error",
|
||||
"translation": "La licencia firmada no es suficientemente larga"
|
||||
},
|
||||
{
|
||||
"id": "utils.license.validate_license.signing.error",
|
||||
"translation": "Encontramos un error al firmar la licencia, err=%v"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.connect_smtp.open.app_error",
|
||||
"translation": "Falla al abrir conexión"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.connect_smtp.open_tls.app_error",
|
||||
"translation": "Falla al abrir una conexión TLS"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.new_client.auth.app_error",
|
||||
"translation": "Falla autenticando contra el servidor SMTP"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.new_client.open.error",
|
||||
"translation": "Falla al abrir la conexión al servidor SMTP %v"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.close.app_error",
|
||||
"translation": "Falla al cerrar la conexión al servidor SMTP"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.from_address.app_error",
|
||||
"translation": "Falla al agregar el correo electrónico desde"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.msg.app_error",
|
||||
"translation": "Falla al escribir el mensaje del correo electrónico"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.msg_data.app_error",
|
||||
"translation": "Falla al agregar la data al mensaje del correo electrónico"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.sending.debug",
|
||||
"translation": "enviano correo electrónico a %v con el asunto '%v'"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.send_mail.to_address.app_error",
|
||||
"translation": "Falla al agregar el correo electrónico para"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.test.configured.error",
|
||||
"translation": "El servidor SMTP parece no estar configurado apropiadamente err=%v details=%v"
|
||||
}
|
||||
]
|
||||
@@ -32,12 +32,12 @@ func InitManualTesting() {
|
||||
|
||||
func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// Let the world know
|
||||
l4g.Info("Setting up for manual test...")
|
||||
l4g.Info(utils.T("manaultesting.manual_test.setup.info"))
|
||||
|
||||
// URL Parameters
|
||||
params, err := url.ParseQuery(r.URL.RawQuery)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("/manual", "Unable to parse URL", "")
|
||||
c.Err = model.NewLocAppError("/manual", "manaultesting.manual_test.parse.app_error", nil, "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
hash := hasher.Sum32()
|
||||
rand.Seed(int64(hash))
|
||||
} else {
|
||||
l4g.Debug("No uid in url")
|
||||
l4g.Debug(utils.T("manaultesting.manual_test.uid.debug"))
|
||||
}
|
||||
|
||||
// Create a client for tests to use
|
||||
@@ -61,7 +61,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
var teamID string
|
||||
var userID string
|
||||
if ok1 && ok2 {
|
||||
l4g.Info("Creating user and team")
|
||||
l4g.Info(utils.T("manaultesting.manual_test.create.info"))
|
||||
// Create team for testing
|
||||
team := &model.Team{
|
||||
DisplayName: teamDisplayName[0],
|
||||
@@ -153,7 +153,7 @@ func getChannelID(channelname string, teamid string, userid string) (id string,
|
||||
// Grab all the channels
|
||||
result := <-api.Srv.Store.Channel().GetChannels(teamid, userid)
|
||||
if result.Err != nil {
|
||||
l4g.Debug("Unable to get channels")
|
||||
l4g.Debug(utils.T("manaultesting.get_channel_id.unable.debug"))
|
||||
return "", false
|
||||
}
|
||||
|
||||
@@ -164,6 +164,6 @@ func getChannelID(channelname string, teamid string, userid string) (id string,
|
||||
return channel.Id, true
|
||||
}
|
||||
}
|
||||
l4g.Debug("Could not find channel: " + channelname + ", " + strconv.Itoa(len(data.Channels)) + " possibilites searched")
|
||||
l4g.Debug(utils.T("manaultesting.get_channel_id.no_found.debug"), channelname, strconv.Itoa(len(data.Channels)))
|
||||
return "", false
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package manualtesting
|
||||
import (
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
const LINK_POST_TEXT = `
|
||||
@@ -20,10 +21,10 @@ https://medium.com/@slackhq/11-useful-tips-for-getting-the-most-of-slack-5dfb3d1
|
||||
`
|
||||
|
||||
func testAutoLink(env TestEnvironment) *model.AppError {
|
||||
l4g.Info("Manual Auto Link Test")
|
||||
l4g.Info(utils.T("manaultesting.test_autolink.info"))
|
||||
channelID, err := getChannelID(model.DEFAULT_CHANNEL, env.CreatedTeamId, env.CreatedUserId)
|
||||
if err != true {
|
||||
return model.NewAppError("/manualtest", "Unable to get channels", "")
|
||||
return model.NewLocAppError("/manualtest", "manaultesting.test_autolink.unable.app_error", nil, "")
|
||||
}
|
||||
|
||||
post := &model.Post{
|
||||
|
||||
@@ -66,7 +66,9 @@ func main() {
|
||||
api.InitApi()
|
||||
web.InitWeb()
|
||||
|
||||
utils.LoadLicense()
|
||||
if model.BuildEnterpriseReady == "true" {
|
||||
utils.LoadLicense()
|
||||
}
|
||||
|
||||
if flagRunCmds {
|
||||
runCmds()
|
||||
|
||||
@@ -34,19 +34,19 @@ type AccessResponse struct {
|
||||
func (ad *AccessData) IsValid() *AppError {
|
||||
|
||||
if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 {
|
||||
return NewAppError("AccessData.IsValid", "Invalid auth code", "")
|
||||
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.auth_code.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(ad.Token) != 26 {
|
||||
return NewAppError("AccessData.IsValid", "Invalid access token", "")
|
||||
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.access_token.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(ad.RefreshToken) > 26 {
|
||||
return NewAppError("AccessData.IsValid", "Invalid refresh token", "")
|
||||
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(ad.RedirectUri) > 256 {
|
||||
return NewAppError("AccessData.IsValid", "Invalid redirect uri", "")
|
||||
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -29,35 +29,35 @@ type AuthData struct {
|
||||
func (ad *AuthData) IsValid() *AppError {
|
||||
|
||||
if len(ad.ClientId) != 26 {
|
||||
return NewAppError("AuthData.IsValid", "Invalid client id", "")
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(ad.UserId) != 26 {
|
||||
return NewAppError("AuthData.IsValid", "Invalid user id", "")
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.user_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(ad.Code) == 0 || len(ad.Code) > 128 {
|
||||
return NewAppError("AuthData.IsValid", "Invalid authorization code", "client_id="+ad.ClientId)
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.auth_code.app_error", nil, "client_id="+ad.ClientId)
|
||||
}
|
||||
|
||||
if ad.ExpiresIn == 0 {
|
||||
return NewAppError("AuthData.IsValid", "Expires in must be set", "")
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.expires.app_error", nil, "")
|
||||
}
|
||||
|
||||
if ad.CreateAt <= 0 {
|
||||
return NewAppError("AuthData.IsValid", "Create at must be a valid time", "client_id="+ad.ClientId)
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId)
|
||||
}
|
||||
|
||||
if len(ad.RedirectUri) > 256 {
|
||||
return NewAppError("AuthData.IsValid", "Invalid redirect uri", "client_id="+ad.ClientId)
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId)
|
||||
}
|
||||
|
||||
if len(ad.State) > 128 {
|
||||
return NewAppError("AuthData.IsValid", "Invalid state", "client_id="+ad.ClientId)
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ad.ClientId)
|
||||
}
|
||||
|
||||
if len(ad.Scope) > 128 {
|
||||
return NewAppError("AuthData.IsValid", "Invalid scope", "client_id="+ad.ClientId)
|
||||
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ad.ClientId)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -64,43 +64,43 @@ func (o *Channel) ExtraEtag(memberLimit int) string {
|
||||
func (o *Channel) IsValid() *AppError {
|
||||
|
||||
if len(o.Id) != 26 {
|
||||
return NewAppError("Channel.IsValid", "Invalid Id", "")
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("Channel.IsValid", "Create at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("Channel.IsValid", "Update at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.DisplayName) > 64 {
|
||||
return NewAppError("Channel.IsValid", "Invalid display name", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.Name) > 64 {
|
||||
return NewAppError("Channel.IsValid", "Invalid name", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if !IsValidChannelIdentifier(o.Name) {
|
||||
return NewAppError("Channel.IsValid", "Name must be 2 or more lowercase alphanumeric characters", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if !(o.Type == CHANNEL_OPEN || o.Type == CHANNEL_PRIVATE || o.Type == CHANNEL_DIRECT) {
|
||||
return NewAppError("Channel.IsValid", "Invalid type", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Header) > 1024 {
|
||||
return NewAppError("Channel.IsValid", "Invalid header", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Purpose) > 128 {
|
||||
return NewAppError("Channel.IsValid", "Invalid purpose", "id="+o.Id)
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.CreatorId) > 26 {
|
||||
return NewAppError("Channel.IsValid", "Invalid creator id", "")
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -53,27 +53,29 @@ func ChannelMemberFromJson(data io.Reader) *ChannelMember {
|
||||
func (o *ChannelMember) IsValid() *AppError {
|
||||
|
||||
if len(o.ChannelId) != 26 {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid channel id", "")
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.UserId) != 26 {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid user id", "")
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
for _, role := range strings.Split(o.Roles, " ") {
|
||||
if !(role == "" || role == CHANNEL_ROLE_ADMIN) {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid role", "role="+role)
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.role.app_error", nil, "role="+role)
|
||||
}
|
||||
}
|
||||
|
||||
notifyLevel := o.NotifyProps["desktop"]
|
||||
if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+notifyLevel)
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error",
|
||||
nil, "notify_level="+notifyLevel)
|
||||
}
|
||||
|
||||
markUnreadLevel := o.NotifyProps["mark_unread"]
|
||||
if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid mark unread level", "mark_unread_level="+markUnreadLevel)
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.unread_level.app_error",
|
||||
nil, "mark_unread_level="+markUnreadLevel)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -56,7 +56,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro
|
||||
rq.Header.Set("Content-Type", contentType)
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode >= 300 {
|
||||
return nil, AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
@@ -72,7 +72,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError)
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode >= 300 {
|
||||
return nil, AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
@@ -92,7 +92,7 @@ func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response,
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode == 304 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode >= 300 {
|
||||
@@ -298,7 +298,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) {
|
||||
sessionToken := getCookie(SESSION_COOKIE_TOKEN, r)
|
||||
|
||||
if c.AuthToken != sessionToken.Value {
|
||||
NewAppError("/users/login", "Authentication tokens didn't match", "")
|
||||
NewLocAppError("/users/login", "model.client.login.app_error", nil, "")
|
||||
}
|
||||
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
@@ -479,7 +479,7 @@ func (c *Client) TestEmail(config *Config) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetAnalytics(teamId, name string) (*Result, *AppError) {
|
||||
func (c *Client) GetTeamAnalytics(teamId, name string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@@ -488,6 +488,15 @@ func (c *Client) GetAnalytics(teamId, name string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/admin/analytics/"+name, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/channels/create", channel.ToJson()); err != nil {
|
||||
return nil, err
|
||||
@@ -735,7 +744,7 @@ func (c *Client) UploadFile(url string, data []byte, contentType string) (*Resul
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode >= 300 {
|
||||
return nil, AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
@@ -757,7 +766,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode >= 300 {
|
||||
return nil, AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
@@ -775,7 +784,7 @@ func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode >= 300 {
|
||||
return nil, AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
|
||||
@@ -359,87 +359,87 @@ func (o *Config) SetDefaults() {
|
||||
func (o *Config) IsValid() *AppError {
|
||||
|
||||
if o.ServiceSettings.MaximumLoginAttempts <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid maximum login attempts for service settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.ServiceSettings.ListenAddress) == 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid listen address for service settings Must be set.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.TeamSettings.MaxUsersPerTeam <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid maximum users per team for team settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.SqlSettings.AtRestEncryptKey) < 32 {
|
||||
return NewAppError("Config.IsValid", "Invalid at rest encrypt key for SQL settings. Must be 32 chars or more.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !(o.SqlSettings.DriverName == DATABASE_DRIVER_MYSQL || o.SqlSettings.DriverName == DATABASE_DRIVER_POSTGRES) {
|
||||
return NewAppError("Config.IsValid", "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_driver.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.SqlSettings.MaxIdleConns <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid maximum idle connection for SQL settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.SqlSettings.DataSource) == 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid data source for SQL settings. Must be set.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.SqlSettings.MaxOpenConns <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid maximum open connection for SQL settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) {
|
||||
return NewAppError("Config.IsValid", "Invalid driver name for file settings. Must be 'local' or 'amazons3'", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.FileSettings.PreviewHeight < 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid preview height for file settings. Must be a zero or positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_height.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.FileSettings.PreviewWidth <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid preview width for file settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_width.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.FileSettings.ProfileHeight <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid profile height for file settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_height.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.FileSettings.ProfileWidth <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid profile width for file settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_width.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.FileSettings.ThumbnailHeight <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid thumbnail height for file settings. Must be a positive number.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_height.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.FileSettings.ThumbnailHeight <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid thumbnail width for file settings. Must be a positive number.", "")
|
||||
if o.FileSettings.ThumbnailWidth <= 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_width.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.FileSettings.PublicLinkSalt) < 32 {
|
||||
return NewAppError("Config.IsValid", "Invalid public link salt for file settings. Must be 32 chars or more.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) {
|
||||
return NewAppError("Config.IsValid", "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.EmailSettings.InviteSalt) < 32 {
|
||||
return NewAppError("Config.IsValid", "Invalid invite salt for email settings. Must be 32 chars or more.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_salt.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.EmailSettings.PasswordResetSalt) < 32 {
|
||||
return NewAppError("Config.IsValid", "Invalid password reset salt for email settings. Must be 32 chars or more.", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_reset_salt.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.RateLimitSettings.MemoryStoreSize <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid memory store size for rate limit settings. Must be a positive number", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.RateLimitSettings.PerSec <= 0 {
|
||||
return NewAppError("Config.IsValid", "Invalid per sec for rate limit settings. Must be a positive number", "")
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_sec.app_error", nil, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -32,11 +32,16 @@ func GetInfoForBytes(filename string, data []byte) (*FileInfo, *AppError) {
|
||||
mimeType = mime.TypeByExtension(extension)
|
||||
}
|
||||
|
||||
if extension != "" && extension[0] == '.' {
|
||||
// the client expects a file extension without the leading period
|
||||
extension = extension[1:]
|
||||
}
|
||||
|
||||
hasPreviewImage := isImage
|
||||
if mimeType == "image/gif" {
|
||||
// just show the gif itself instead of a preview image for animated gifs
|
||||
if gifImage, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
|
||||
return nil, NewAppError("GetInfoForBytes", "Could not decode gif.", "filename="+filename)
|
||||
return nil, NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "filename="+filename)
|
||||
} else {
|
||||
hasPreviewImage = len(gifImage.Image) == 1
|
||||
}
|
||||
@@ -45,7 +50,7 @@ func GetInfoForBytes(filename string, data []byte) (*FileInfo, *AppError) {
|
||||
return &FileInfo{
|
||||
Filename: filename,
|
||||
Size: size,
|
||||
Extension: extension[1:],
|
||||
Extension: extension,
|
||||
MimeType: mimeType,
|
||||
HasPreviewImage: hasPreviewImage,
|
||||
}, nil
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestGetInfoForBytes(t *testing.T) {
|
||||
} else if info.Size != 1000 {
|
||||
t.Fatalf("Got incorrect size: %v", info.Size)
|
||||
} else if info.Extension != "txt" {
|
||||
t.Fatalf("Git incorrect file extension: %v", info.Extension)
|
||||
t.Fatalf("Got incorrect file extension: %v", info.Extension)
|
||||
} else if info.MimeType != "text/plain; charset=utf-8" {
|
||||
t.Fatalf("Got incorrect mime type: %v", info.MimeType)
|
||||
} else if info.HasPreviewImage {
|
||||
@@ -33,7 +33,7 @@ func TestGetInfoForBytes(t *testing.T) {
|
||||
} else if info.Size != 1000 {
|
||||
t.Fatalf("Got incorrect size: %v", info.Size)
|
||||
} else if info.Extension != "png" {
|
||||
t.Fatalf("Git incorrect file extension: %v", info.Extension)
|
||||
t.Fatalf("Got incorrect file extension: %v", info.Extension)
|
||||
} else if info.MimeType != "image/png" {
|
||||
t.Fatalf("Got incorrect mime type: %v", info.MimeType)
|
||||
} else if !info.HasPreviewImage {
|
||||
@@ -49,7 +49,7 @@ func TestGetInfoForBytes(t *testing.T) {
|
||||
} else if info.Size != 35 {
|
||||
t.Fatalf("Got incorrect size: %v", info.Size)
|
||||
} else if info.Extension != "gif" {
|
||||
t.Fatalf("Git incorrect file extension: %v", info.Extension)
|
||||
t.Fatalf("Got incorrect file extension: %v", info.Extension)
|
||||
} else if info.MimeType != "image/gif" {
|
||||
t.Fatalf("Got incorrect mime type: %v", info.MimeType)
|
||||
} else if !info.HasPreviewImage {
|
||||
@@ -67,10 +67,24 @@ func TestGetInfoForBytes(t *testing.T) {
|
||||
} else if info.Size != 38689 {
|
||||
t.Fatalf("Got incorrect size: %v", info.Size)
|
||||
} else if info.Extension != "gif" {
|
||||
t.Fatalf("Git incorrect file extension: %v", info.Extension)
|
||||
t.Fatalf("Got incorrect file extension: %v", info.Extension)
|
||||
} else if info.MimeType != "image/gif" {
|
||||
t.Fatalf("Got incorrect mime type: %v", info.MimeType)
|
||||
} else if info.HasPreviewImage {
|
||||
t.Fatalf("Got HasPreviewImage = true for animated gif")
|
||||
}
|
||||
|
||||
if info, err := GetInfoForBytes("filewithoutextension", fakeFile); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if info.Filename != "filewithoutextension" {
|
||||
t.Fatalf("Got incorrect filename: %v", info.Filename)
|
||||
} else if info.Size != 1000 {
|
||||
t.Fatalf("Got incorrect size: %v", info.Size)
|
||||
} else if info.Extension != "" {
|
||||
t.Fatalf("Got incorrect file extension: %v", info.Extension)
|
||||
} else if info.MimeType != "" {
|
||||
t.Fatalf("Got incorrect mime type: %v", info.MimeType)
|
||||
} else if info.HasPreviewImage {
|
||||
t.Fatalf("Got HasPreviewImage = true for non-image file")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,27 +76,27 @@ func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook {
|
||||
func (o *IncomingWebhook) IsValid() *AppError {
|
||||
|
||||
if len(o.Id) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid Id", "")
|
||||
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Create at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Update at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.UserId) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid user id", "")
|
||||
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.ChannelId) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid channel id", "")
|
||||
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.TeamId) != 26 {
|
||||
return NewAppError("IncomingWebhook.IsValid", "Invalid channel id", "")
|
||||
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -34,39 +34,39 @@ type OAuthApp struct {
|
||||
func (a *OAuthApp) IsValid() *AppError {
|
||||
|
||||
if len(a.Id) != 26 {
|
||||
return NewAppError("OAuthApp.IsValid", "Invalid app id", "")
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if a.CreateAt == 0 {
|
||||
return NewAppError("OAuthApp.IsValid", "Create at must be a valid time", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
if a.UpdateAt == 0 {
|
||||
return NewAppError("OAuthApp.IsValid", "Update at must be a valid time", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
if len(a.CreatorId) != 26 {
|
||||
return NewAppError("OAuthApp.IsValid", "Invalid creator id", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 {
|
||||
return NewAppError("OAuthApp.IsValid", "Invalid client secret", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
if len(a.Name) == 0 || len(a.Name) > 64 {
|
||||
return NewAppError("OAuthApp.IsValid", "Invalid name", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 {
|
||||
return NewAppError("OAuthApp.IsValid", "Invalid callback urls", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
if len(a.Homepage) == 0 || len(a.Homepage) > 256 {
|
||||
return NewAppError("OAuthApp.IsValid", "Invalid homepage", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(a.Description) > 512 {
|
||||
return NewAppError("OAuthApp.IsValid", "Invalid description", "app_id="+a.Id)
|
||||
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -65,44 +65,44 @@ func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook {
|
||||
func (o *OutgoingWebhook) IsValid() *AppError {
|
||||
|
||||
if len(o.Id) != 26 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid Id", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.Token) != 26 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid token", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Create at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Update at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.CreatorId) != 26 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid user id", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid channel id", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.TeamId) != 26 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid team id", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid trigger words", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid callback urls", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "")
|
||||
}
|
||||
|
||||
for _, callback := range o.CallbackURLs {
|
||||
if !IsValidHttpUrl(callback) {
|
||||
return NewAppError("OutgoingWebhook.IsValid", "Invalid callback URLs. Each must be a valid URL and start with http:// or https://", "")
|
||||
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,60 +62,60 @@ func (o *Post) Etag() string {
|
||||
func (o *Post) IsValid() *AppError {
|
||||
|
||||
if len(o.Id) != 26 {
|
||||
return NewAppError("Post.IsValid", "Invalid Id", "")
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("Post.IsValid", "Create at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.create_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("Post.IsValid", "Update at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.update_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.UserId) != 26 {
|
||||
return NewAppError("Post.IsValid", "Invalid user id", "")
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.user_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.ChannelId) != 26 {
|
||||
return NewAppError("Post.IsValid", "Invalid channel id", "")
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.channel_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !(len(o.RootId) == 26 || len(o.RootId) == 0) {
|
||||
return NewAppError("Post.IsValid", "Invalid root id", "")
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.root_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !(len(o.ParentId) == 26 || len(o.ParentId) == 0) {
|
||||
return NewAppError("Post.IsValid", "Invalid parent id", "")
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.parent_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.ParentId) == 26 && len(o.RootId) == 0 {
|
||||
return NewAppError("Post.IsValid", "Invalid root id must be set if parent id set", "")
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.root_parent.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !(len(o.OriginalId) == 26 || len(o.OriginalId) == 0) {
|
||||
return NewAppError("Post.IsValid", "Invalid original id", "")
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Message) > 4000 {
|
||||
return NewAppError("Post.IsValid", "Invalid message", "id="+o.Id)
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.msg.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Hashtags) > 1000 {
|
||||
return NewAppError("Post.IsValid", "Invalid hashtags", "id="+o.Id)
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
// should be removed once more message types are supported
|
||||
if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) {
|
||||
return NewAppError("Post.IsValid", "Invalid type", "id="+o.Type)
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > 4000 {
|
||||
return NewAppError("Post.IsValid", "Invalid filenames", "id="+o.Id)
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > 8000 {
|
||||
return NewAppError("Post.IsValid", "Invalid props", "id="+o.Id)
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -47,19 +47,19 @@ func PreferenceFromJson(data io.Reader) *Preference {
|
||||
|
||||
func (o *Preference) IsValid() *AppError {
|
||||
if len(o.UserId) != 26 {
|
||||
return NewAppError("Preference.IsValid", "Invalid user id", "user_id="+o.UserId)
|
||||
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.id.app_error", nil, "user_id="+o.UserId)
|
||||
}
|
||||
|
||||
if len(o.Category) == 0 || len(o.Category) > 32 {
|
||||
return NewAppError("Preference.IsValid", "Invalid category", "category="+o.Category)
|
||||
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category)
|
||||
}
|
||||
|
||||
if len(o.Name) == 0 || len(o.Name) > 32 {
|
||||
return NewAppError("Preference.IsValid", "Invalid name", "name="+o.Name)
|
||||
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Value) > 128 {
|
||||
return NewAppError("Preference.IsValid", "Value is too long", "value="+o.Value)
|
||||
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -104,51 +104,51 @@ func (o *Team) Etag() string {
|
||||
func (o *Team) IsValid(restrictTeamNames bool) *AppError {
|
||||
|
||||
if len(o.Id) != 26 {
|
||||
return NewAppError("Team.IsValid", "Invalid Id", "")
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("Team.IsValid", "Create at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.create_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("Team.IsValid", "Update at must be a valid time", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.update_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.Email) > 128 {
|
||||
return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.Email) > 0 && !IsValidEmail(o.Email) {
|
||||
return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.DisplayName) == 0 || utf8.RuneCountInString(o.DisplayName) > 64 {
|
||||
return NewAppError("Team.IsValid", "Invalid name", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.name.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.Name) > 64 {
|
||||
return NewAppError("Team.IsValid", "Invalid URL Identifier", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if restrictTeamNames && IsReservedTeamName(o.Name) {
|
||||
return NewAppError("Team.IsValid", "This URL is unavailable. Please try another.", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if !IsValidTeamName(o.Name) {
|
||||
return NewAppError("Team.IsValid", "Name must be 4 or more lowercase alphanumeric characters", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.characters.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) {
|
||||
return NewAppError("Team.IsValid", "Invalid type", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.CompanyName) > 64 {
|
||||
return NewAppError("Team.IsValid", "Invalid company name", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.AllowedDomains) > 500 {
|
||||
return NewAppError("Team.IsValid", "Invalid allowed domains", "id="+o.Id)
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.domains.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -61,59 +61,59 @@ type User struct {
|
||||
func (u *User) IsValid() *AppError {
|
||||
|
||||
if len(u.Id) != 26 {
|
||||
return NewAppError("User.IsValid", "Invalid user id", "")
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if u.CreateAt == 0 {
|
||||
return NewAppError("User.IsValid", "Create at must be a valid time", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.create_at.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if u.UpdateAt == 0 {
|
||||
return NewAppError("User.IsValid", "Update at must be a valid time", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.update_at.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if len(u.TeamId) != 26 {
|
||||
return NewAppError("User.IsValid", "Invalid team id", "")
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.team_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !IsValidUsername(u.Username) {
|
||||
return NewAppError("User.IsValid", "Invalid username", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.username.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if len(u.Email) > 128 || len(u.Email) == 0 {
|
||||
return NewAppError("User.IsValid", "Invalid email", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.email.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.Nickname) > 64 {
|
||||
return NewAppError("User.IsValid", "Invalid nickname", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.nickname.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.FirstName) > 64 {
|
||||
return NewAppError("User.IsValid", "Invalid first name", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.LastName) > 64 {
|
||||
return NewAppError("User.IsValid", "Invalid last name", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.last_name.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if len(u.Password) > 128 {
|
||||
return NewAppError("User.IsValid", "Invalid password", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.pwd.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if len(u.AuthData) > 128 {
|
||||
return NewAppError("User.IsValid", "Invalid auth data", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if len(u.AuthData) > 0 && len(u.AuthService) == 0 {
|
||||
return NewAppError("User.IsValid", "Invalid user, auth data must be set with auth type", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_type.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if len(u.Password) > 0 && len(u.AuthData) > 0 {
|
||||
return NewAppError("User.IsValid", "Invalid user, password and auth data cannot both be set", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if len(u.ThemeProps) > 2000 {
|
||||
return NewAppError("User.IsValid", "Invalid theme", "user_id="+u.Id)
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -236,7 +236,6 @@ func (u *User) Sanitize(options map[string]bool) {
|
||||
}
|
||||
|
||||
func (u *User) ClearNonProfileFields() {
|
||||
u.CreateAt = 0
|
||||
u.UpdateAt = 0
|
||||
u.Password = ""
|
||||
u.AuthData = ""
|
||||
|
||||
@@ -67,11 +67,7 @@ func AppErrorFromJson(data io.Reader) *AppError {
|
||||
if err == nil {
|
||||
return &er
|
||||
} else {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(data)
|
||||
s := buf.String()
|
||||
|
||||
return NewAppError("AppErrorFromJson", "could not decode", err.Error()+" "+s)
|
||||
return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -582,7 +582,16 @@ func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
count, err := s.GetReplica().SelectInt("SELECT count(*) FROM ChannelMembers WHERE ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId})
|
||||
count, err := s.GetReplica().SelectInt(`
|
||||
SELECT
|
||||
count(*)
|
||||
FROM
|
||||
ChannelMembers,
|
||||
Users
|
||||
WHERE
|
||||
ChannelMembers.UserId = Users.Id
|
||||
AND ChannelMembers.ChannelId = :ChannelId
|
||||
AND Users.DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.GetMemberCount", "We couldn't get the channel member count", "channel_id="+channelId+", "+err.Error())
|
||||
} else {
|
||||
@@ -869,15 +878,13 @@ func (s SqlChannelStore) AnalyticsTypeCount(teamId string, channelType string) S
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
v, err := s.GetReplica().SelectInt(
|
||||
`SELECT
|
||||
COUNT(Id) AS Value
|
||||
FROM
|
||||
Channels
|
||||
WHERE
|
||||
TeamId = :TeamId
|
||||
AND Type = :ChannelType`,
|
||||
map[string]interface{}{"TeamId": teamId, "ChannelType": channelType})
|
||||
query := "SELECT COUNT(Id) AS Value FROM Channels WHERE Type = :ChannelType"
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND TeamId = :TeamId"
|
||||
}
|
||||
|
||||
v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId, "ChannelType": channelType})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.AnalyticsTypeCount", "We couldn't get channel type counts", err.Error())
|
||||
} else {
|
||||
|
||||
@@ -750,3 +750,109 @@ func TestChannelStoreIncrementMentionCount(t *testing.T) {
|
||||
t.Fatal("failed to update")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMemberCount(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
teamId := model.NewId()
|
||||
|
||||
c1 := model.Channel{
|
||||
TeamId: teamId,
|
||||
DisplayName: "Channel1",
|
||||
Name: "a" + model.NewId() + "b",
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}
|
||||
Must(store.Channel().Save(&c1))
|
||||
|
||||
c2 := model.Channel{
|
||||
TeamId: teamId,
|
||||
DisplayName: "Channel2",
|
||||
Name: "a" + model.NewId() + "b",
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}
|
||||
Must(store.Channel().Save(&c2))
|
||||
|
||||
t.Logf("c1.Id = %v", c1.Id)
|
||||
|
||||
u1 := model.User{
|
||||
TeamId: teamId,
|
||||
Email: model.NewId(),
|
||||
DeleteAt: 0,
|
||||
}
|
||||
Must(store.User().Save(&u1))
|
||||
|
||||
m1 := model.ChannelMember{
|
||||
ChannelId: c1.Id,
|
||||
UserId: u1.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
if result := <-store.Channel().GetMemberCount(c1.Id); result.Err != nil {
|
||||
t.Fatal("failed to get member count: %v", result.Err)
|
||||
} else if result.Data.(int64) != 1 {
|
||||
t.Fatal("got incorrect member count %v", result.Data)
|
||||
}
|
||||
|
||||
u2 := model.User{
|
||||
TeamId: teamId,
|
||||
Email: model.NewId(),
|
||||
DeleteAt: 0,
|
||||
}
|
||||
Must(store.User().Save(&u2))
|
||||
|
||||
m2 := model.ChannelMember{
|
||||
ChannelId: c1.Id,
|
||||
UserId: u2.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}
|
||||
Must(store.Channel().SaveMember(&m2))
|
||||
|
||||
if result := <-store.Channel().GetMemberCount(c1.Id); result.Err != nil {
|
||||
t.Fatal("failed to get member count: %v", result.Err)
|
||||
} else if result.Data.(int64) != 2 {
|
||||
t.Fatal("got incorrect member count %v", result.Data)
|
||||
}
|
||||
|
||||
// make sure members of other channels aren't counted
|
||||
u3 := model.User{
|
||||
TeamId: teamId,
|
||||
Email: model.NewId(),
|
||||
DeleteAt: 0,
|
||||
}
|
||||
Must(store.User().Save(&u3))
|
||||
|
||||
m3 := model.ChannelMember{
|
||||
ChannelId: c2.Id,
|
||||
UserId: u3.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}
|
||||
Must(store.Channel().SaveMember(&m3))
|
||||
|
||||
if result := <-store.Channel().GetMemberCount(c1.Id); result.Err != nil {
|
||||
t.Fatal("failed to get member count: %v", result.Err)
|
||||
} else if result.Data.(int64) != 2 {
|
||||
t.Fatal("got incorrect member count %v", result.Data)
|
||||
}
|
||||
|
||||
// make sure inactive users aren't counted
|
||||
u4 := model.User{
|
||||
TeamId: teamId,
|
||||
Email: model.NewId(),
|
||||
DeleteAt: 10000,
|
||||
}
|
||||
Must(store.User().Save(&u4))
|
||||
|
||||
m4 := model.ChannelMember{
|
||||
ChannelId: c1.Id,
|
||||
UserId: u4.Id,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}
|
||||
Must(store.Channel().SaveMember(&m4))
|
||||
|
||||
if result := <-store.Channel().GetMemberCount(c1.Id); result.Err != nil {
|
||||
t.Fatal("failed to get member count: %v", result.Err)
|
||||
} else if result.Data.(int64) != 2 {
|
||||
t.Fatal("got incorrect member count %v", result.Data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,9 +805,13 @@ func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChan
|
||||
FROM
|
||||
Posts, Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId
|
||||
AND Posts.CreateAt <= :EndTime
|
||||
Posts.ChannelId = Channels.Id`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND Channels.TeamId = :TeamId"
|
||||
}
|
||||
|
||||
query += ` AND Posts.CreateAt <= :EndTime
|
||||
ORDER BY Name DESC) AS t1
|
||||
GROUP BY Name
|
||||
ORDER BY Name DESC
|
||||
@@ -824,9 +828,13 @@ func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChan
|
||||
FROM
|
||||
Posts, Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId
|
||||
AND Posts.CreateAt <= :EndTime
|
||||
Posts.ChannelId = Channels.Id`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND Channels.TeamId = :TeamId"
|
||||
}
|
||||
|
||||
query += ` AND Posts.CreateAt <= :EndTime
|
||||
ORDER BY Name DESC) AS t1
|
||||
GROUP BY Name
|
||||
ORDER BY Name DESC
|
||||
@@ -869,9 +877,13 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
|
||||
FROM
|
||||
Posts, Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId
|
||||
AND Posts.CreateAt <= :EndTime
|
||||
Posts.ChannelId = Channels.Id`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND Channels.TeamId = :TeamId"
|
||||
}
|
||||
|
||||
query += ` AND Posts.CreateAt <= :EndTime
|
||||
AND Posts.CreateAt >= :StartTime) AS t1
|
||||
GROUP BY Name
|
||||
ORDER BY Name DESC
|
||||
@@ -888,9 +900,13 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
|
||||
FROM
|
||||
Posts, Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId
|
||||
AND Posts.CreateAt <= :EndTime
|
||||
Posts.ChannelId = Channels.Id`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND Channels.TeamId = :TeamId"
|
||||
}
|
||||
|
||||
query += ` AND Posts.CreateAt <= :EndTime
|
||||
AND Posts.CreateAt >= :StartTime) AS t1
|
||||
GROUP BY Name
|
||||
ORDER BY Name DESC
|
||||
@@ -924,16 +940,20 @@ func (s SqlPostStore) AnalyticsPostCount(teamId string) StoreChannel {
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
v, err := s.GetReplica().SelectInt(
|
||||
query :=
|
||||
`SELECT
|
||||
COUNT(Posts.Id) AS Value
|
||||
FROM
|
||||
Posts,
|
||||
Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId`,
|
||||
map[string]interface{}{"TeamId": teamId})
|
||||
Posts.ChannelId = Channels.Id`
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " AND Channels.TeamId = :TeamId"
|
||||
}
|
||||
|
||||
v, err := s.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "We couldn't get post counts", err.Error())
|
||||
} else {
|
||||
|
||||
@@ -600,3 +600,30 @@ func (us SqlUserStore) PermanentDelete(userId string) StoreChannel {
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (us SqlUserStore) AnalyticsUniqueUserCount(teamId string) StoreChannel {
|
||||
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
query := "SELECT COUNT(DISTINCT Email) FROM Users"
|
||||
|
||||
if len(teamId) > 0 {
|
||||
query += " WHERE TeamId = :TeamId"
|
||||
}
|
||||
|
||||
v, err := us.GetReplica().SelectInt(query, map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlUserStore.AnalyticsUniqueUserCount", "We couldn't get the unique user count", err.Error())
|
||||
} else {
|
||||
result.Data = v
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ type UserStore interface {
|
||||
GetTotalActiveUsersCount() StoreChannel
|
||||
GetSystemAdminProfiles() StoreChannel
|
||||
PermanentDelete(userId string) StoreChannel
|
||||
AnalyticsUniqueUserCount(teamId string) StoreChannel
|
||||
}
|
||||
|
||||
type SessionStore interface {
|
||||
|
||||
@@ -118,12 +118,14 @@ func GetLogFileLocation(fileLocation string) string {
|
||||
func SaveConfig(fileName string, config *model.Config) *model.AppError {
|
||||
b, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return model.NewAppError("SaveConfig", "An error occurred while saving the file to "+fileName, err.Error())
|
||||
return model.NewLocAppError("SaveConfig", "utils.config.save_config.saving.app_error",
|
||||
map[string]interface{}{"Filename": fileName}, err.Error())
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(fileName, b, 0644)
|
||||
if err != nil {
|
||||
return model.NewAppError("SaveConfig", "An error occurred while saving the file to "+fileName, err.Error())
|
||||
return model.NewLocAppError("SaveConfig", "utils.config.save_config.saving.app_error",
|
||||
map[string]interface{}{"Filename": fileName}, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -138,18 +140,21 @@ func LoadConfig(fileName string) {
|
||||
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
panic("Error opening config file=" + fileName + ", err=" + err.Error())
|
||||
panic(T("utils.config.load_config.opening.panic",
|
||||
map[string]interface{}{"Filename": fileName, "Error": err.Error()}))
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(file)
|
||||
config := model.Config{}
|
||||
err = decoder.Decode(&config)
|
||||
if err != nil {
|
||||
panic("Error decoding config file=" + fileName + ", err=" + err.Error())
|
||||
panic(T("utils.config.load_config.decoding.panic",
|
||||
map[string]interface{}{"Filename": fileName, "Error": err.Error()}))
|
||||
}
|
||||
|
||||
if info, err := file.Stat(); err != nil {
|
||||
panic("Error getting config info file=" + fileName + ", err=" + err.Error())
|
||||
panic(T("utils.config.load_config.getting.panic",
|
||||
map[string]interface{}{"Filename": fileName, "Error": err.Error()}))
|
||||
} else {
|
||||
CfgLastModified = info.ModTime().Unix()
|
||||
CfgFileName = fileName
|
||||
@@ -158,7 +163,8 @@ func LoadConfig(fileName string) {
|
||||
config.SetDefaults()
|
||||
|
||||
if err := config.IsValid(); err != nil {
|
||||
panic("Error validating config file=" + fileName + ", err=" + err.Message)
|
||||
panic(T("utils.config.load_config.validating.panic",
|
||||
map[string]interface{}{"Filename": fileName, "Error": err.Message}))
|
||||
}
|
||||
|
||||
configureLog(&config.LogSettings)
|
||||
|
||||
@@ -9,4 +9,5 @@ import (
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
LoadConfig("config.json")
|
||||
InitTranslations()
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ NxpC+5KFhU+xSeeklNqwCgnlOyZ7qSTxmdJHb+60SwuYnnGIYzLJhY4LYDr4J+KR
|
||||
func LoadLicense() {
|
||||
file, err := os.Open(LicenseLocation())
|
||||
if err != nil {
|
||||
l4g.Warn("Unable to open/find license file")
|
||||
l4g.Warn(T("utils.license.load_license.open_find.warn"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
@@ -55,9 +55,10 @@ func LoadLicense() {
|
||||
if success, licenseStr := ValidateLicense(buf.Bytes()); success {
|
||||
license := model.LicenseFromJson(strings.NewReader(licenseStr))
|
||||
SetLicense(license)
|
||||
return
|
||||
}
|
||||
|
||||
l4g.Warn("No valid enterprise license found")
|
||||
l4g.Warn(T("utils.license.load_license.invalid.warn"))
|
||||
}
|
||||
|
||||
func SetLicense(license *model.License) bool {
|
||||
@@ -83,7 +84,7 @@ func RemoveLicense() bool {
|
||||
ClientLicense = getClientLicense(License)
|
||||
|
||||
if err := os.Remove(LicenseLocation()); err != nil {
|
||||
l4g.Error("Unable to remove license file, err=%v", err.Error())
|
||||
l4g.Error(T("utils.license.remove_license.unable.error"), err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -95,17 +96,17 @@ func ValidateLicense(signed []byte) (bool, string) {
|
||||
|
||||
_, err := base64.StdEncoding.Decode(decoded, signed)
|
||||
if err != nil {
|
||||
l4g.Error("Encountered error decoding license, err=%v", err.Error())
|
||||
l4g.Error(T("utils.license.validate_license.decode.error"), err.Error())
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if len(decoded) <= 256 {
|
||||
l4g.Error("Signed license not long enough")
|
||||
l4g.Error(T("utils.license.validate_license.not_long.error"))
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// remove null terminator
|
||||
if decoded[len(decoded)-1] == byte(0) {
|
||||
for decoded[len(decoded)-1] == byte(0) {
|
||||
decoded = decoded[:len(decoded)-1]
|
||||
}
|
||||
|
||||
@@ -116,7 +117,7 @@ func ValidateLicense(signed []byte) (bool, string) {
|
||||
|
||||
public, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
l4g.Error("Encountered error signing license, err=%v", err.Error())
|
||||
l4g.Error(T("utils.license.validate_license.signing.error"), err.Error())
|
||||
return false, ""
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ func ValidateLicense(signed []byte) (bool, string) {
|
||||
|
||||
err = rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA512, d, signature)
|
||||
if err != nil {
|
||||
l4g.Error("Invalid signature, err=%v", err.Error())
|
||||
l4g.Error(T("utils.license.validate_license.invalid.error"), err.Error())
|
||||
return false, ""
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewLru(size int) *Cache {
|
||||
|
||||
func NewLruWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
|
||||
if size <= 0 {
|
||||
return nil, errors.New("Must provide a positive size")
|
||||
return nil, errors.New(T("utils.iru.with_evict"))
|
||||
}
|
||||
c := &Cache{
|
||||
size: size,
|
||||
|
||||
@@ -34,12 +34,12 @@ func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
|
||||
|
||||
conn, err = tls.Dial("tcp", config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort, tlsconfig)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SendMail", "Failed to open TLS connection", err.Error())
|
||||
return nil, model.NewLocAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error())
|
||||
}
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SendMail", "Failed to open connection", err.Error())
|
||||
return nil, model.NewLocAppError("SendMail", "utils.mail.connect_smtp.open.app_error", nil, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +49,15 @@ func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
|
||||
func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) {
|
||||
c, err := smtp.NewClient(conn, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort)
|
||||
if err != nil {
|
||||
l4g.Error("Failed to open a connection to SMTP server %v", err)
|
||||
return nil, model.NewAppError("SendMail", "Failed to open TLS connection", err.Error())
|
||||
l4g.Error(T("utils.mail.new_client.open.error"), err)
|
||||
return nil, model.NewLocAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error())
|
||||
}
|
||||
// GO does not support plain auth over a non encrypted connection.
|
||||
// so if not tls then no auth
|
||||
auth := smtp.PlainAuth("", config.EmailSettings.SMTPUsername, config.EmailSettings.SMTPPassword, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort)
|
||||
if config.EmailSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
||||
if err = c.Auth(auth); err != nil {
|
||||
return nil, model.NewAppError("SendMail", "Failed to authenticate on SMTP server", err.Error())
|
||||
return nil, model.NewLocAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error())
|
||||
}
|
||||
} else if config.EmailSettings.ConnectionSecurity == model.CONN_SECURITY_STARTTLS {
|
||||
tlsconfig := &tls.Config{
|
||||
@@ -66,7 +66,7 @@ func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.Ap
|
||||
}
|
||||
c.StartTLS(tlsconfig)
|
||||
if err = c.Auth(auth); err != nil {
|
||||
return nil, model.NewAppError("SendMail", "Failed to authenticate on SMTP server", err.Error())
|
||||
return nil, model.NewLocAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error())
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
@@ -79,14 +79,14 @@ func TestConnection(config *model.Config) {
|
||||
|
||||
conn, err1 := connectToSMTPServer(config)
|
||||
if err1 != nil {
|
||||
l4g.Error("SMTP server settings do not appear to be configured properly err=%v details=%v", err1.Message, err1.DetailedError)
|
||||
l4g.Error(T("utils.mail.test.configured.error"), err1.Message, err1.DetailedError)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err2 := newSMTPClient(conn, config)
|
||||
if err2 != nil {
|
||||
l4g.Error("SMTP connection settings do not appear to be configured properly err=%v details=%v", err2.Message, err2.DetailedError)
|
||||
l4g.Error(T("utils.mail.test.configured.error"), err2.Message, err2.DetailedError)
|
||||
return
|
||||
}
|
||||
defer c.Quit()
|
||||
@@ -102,7 +102,7 @@ func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.
|
||||
return nil
|
||||
}
|
||||
|
||||
l4g.Debug("sending mail to " + to + " with subject of '" + subject + "'")
|
||||
l4g.Debug(T("utils.mail.send_mail.sending.debug"), to, subject)
|
||||
|
||||
fromMail := mail.Address{config.EmailSettings.FeedbackName, config.EmailSettings.FeedbackEmail}
|
||||
toMail := mail.Address{"", to}
|
||||
@@ -136,26 +136,26 @@ func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.
|
||||
defer c.Close()
|
||||
|
||||
if err := c.Mail(fromMail.Address); err != nil {
|
||||
return model.NewAppError("SendMail", "Failed to add from email address", err.Error())
|
||||
return model.NewLocAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
if err := c.Rcpt(toMail.Address); err != nil {
|
||||
return model.NewAppError("SendMail", "Failed to add to email address", err.Error())
|
||||
return model.NewLocAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return model.NewAppError("SendMail", "Failed to add email messsage data", err.Error())
|
||||
return model.NewLocAppError("SendMail", "utils.mail.send_mail.msg_data.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(message))
|
||||
if err != nil {
|
||||
return model.NewAppError("SendMail", "Failed to write email message", err.Error())
|
||||
return model.NewLocAppError("SendMail", "utils.mail.send_mail.msg.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return model.NewAppError("SendMail", "Failed to close connection to SMTP server", err.Error())
|
||||
return model.NewLocAppError("SendMail", "utils.mail.send_mail.close.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -100,8 +100,12 @@ export default class ActivityLogModal extends React.Component {
|
||||
|
||||
if (currentSession.props.platform === 'Windows') {
|
||||
devicePicture = 'fa fa-windows';
|
||||
} else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
|
||||
} else if (currentSession.props.platform === 'Macintosh' ||
|
||||
currentSession.props.platform === 'iPhone') {
|
||||
devicePicture = 'fa fa-apple';
|
||||
} else if (currentSession.props.platform.browser.indexOf('Mattermost/') === 0) {
|
||||
devicePicture = 'fa fa-apple';
|
||||
devicePlatform = 'iPhone';
|
||||
} else if (currentSession.props.platform === 'Linux') {
|
||||
if (currentSession.props.os.indexOf('Android') >= 0) {
|
||||
devicePlatform = 'Android';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import AdminSidebar from './admin_sidebar.jsx';
|
||||
@@ -23,6 +23,7 @@ import TeamUsersTab from './team_users.jsx';
|
||||
import TeamAnalyticsTab from './team_analytics.jsx';
|
||||
import LdapSettingsTab from './ldap_settings.jsx';
|
||||
import LicenseSettingsTab from './license_settings.jsx';
|
||||
import SystemAnalyticsTab from './system_analytics.jsx';
|
||||
|
||||
export default class AdminController extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -45,7 +46,7 @@ export default class AdminController extends React.Component {
|
||||
config: AdminStore.getConfig(),
|
||||
teams: AdminStore.getAllTeams(),
|
||||
selectedTeams,
|
||||
selected: props.tab || 'service_settings',
|
||||
selected: props.tab || 'system_analytics',
|
||||
selectedTeam: props.teamId || null
|
||||
};
|
||||
|
||||
@@ -165,6 +166,8 @@ export default class AdminController extends React.Component {
|
||||
if (this.state.teams) {
|
||||
tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||
}
|
||||
} else if (this.state.selected === 'system_analytics') {
|
||||
tab = <SystemAnalyticsTab />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,25 @@ export default class AdminSidebar extends React.Component {
|
||||
<div className='nav-pills__container'>
|
||||
<ul className='nav nav-pills nav-stacked'>
|
||||
<li>
|
||||
<ul className='nav nav__sub-menu'>
|
||||
<li>
|
||||
<h4>
|
||||
<span className='icon fa fa-gear'></span>
|
||||
<span>{'SITE REPORTS'}</span>
|
||||
</h4>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className='nav nav__sub-menu padded'>
|
||||
<li>
|
||||
<a
|
||||
href='#'
|
||||
className={this.isSelected('system_analytics')}
|
||||
onClick={this.handleClick.bind(this, 'system_analytics', null)}
|
||||
>
|
||||
{'View Statistics'}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className='nav nav__sub-menu'>
|
||||
<li>
|
||||
<h4>
|
||||
|
||||
279
web/react/components/admin_console/analytics.jsx
Обычный файл
279
web/react/components/admin_console/analytics.jsx
Обычный файл
@@ -0,0 +1,279 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
import LineChart from './line_chart.jsx';
|
||||
|
||||
var Tooltip = ReactBootstrap.Tooltip;
|
||||
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
||||
|
||||
export default class Analytics extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() { // in the future, break down these into smaller components
|
||||
var serverError = '';
|
||||
if (this.props.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.props.serverError}</label></div>;
|
||||
}
|
||||
|
||||
var totalCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Total Users'}<i className='fa fa-users'/></div>
|
||||
<div className='content'>{this.props.uniqueUserCount == null ? 'Loading...' : this.props.uniqueUserCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openChannelCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Public Channels'}<i className='fa fa-globe'/></div>
|
||||
<div className='content'>{this.props.channelOpenCount == null ? 'Loading...' : this.props.channelOpenCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openPrivateCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Private Groups'}<i className='fa fa-lock'/></div>
|
||||
<div className='content'>{this.props.channelPrivateCount == null ? 'Loading...' : this.props.channelPrivateCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var postCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Total Posts'}<i className='fa fa-comment'/></div>
|
||||
<div className='content'>{this.props.postCount == null ? 'Loading...' : this.props.postCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Total Posts'}</div>
|
||||
<div className='content'>{'Loading...'}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.postCountsDay != null) {
|
||||
let content;
|
||||
if (this.props.postCountsDay.labels.length === 0) {
|
||||
content = 'Not enough data for a meaningful representation.';
|
||||
} else {
|
||||
content = (
|
||||
<LineChart
|
||||
data={this.props.postCountsDay}
|
||||
width='740'
|
||||
height='225'
|
||||
/>
|
||||
);
|
||||
}
|
||||
postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Total Posts'}</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Active Users With Posts'}</div>
|
||||
<div className='content'>{'Loading...'}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.userCountsWithPostsDay != null) {
|
||||
let content;
|
||||
if (this.props.userCountsWithPostsDay.labels.length === 0) {
|
||||
content = 'Not enough data for a meaningful representation.';
|
||||
} else {
|
||||
content = (
|
||||
<LineChart
|
||||
data={this.props.userCountsWithPostsDay}
|
||||
width='740'
|
||||
height='225'
|
||||
/>
|
||||
);
|
||||
}
|
||||
usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Active Users With Posts'}</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let recentActiveUser;
|
||||
if (this.props.recentActiveUsers != null) {
|
||||
let content;
|
||||
if (this.props.recentActiveUsers.length === 0) {
|
||||
content = 'Loading...';
|
||||
} else {
|
||||
content = (
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
this.props.recentActiveUsers.map((user) => {
|
||||
const tooltip = (
|
||||
<Tooltip id={'recent-user-email-tooltip-' + user.id}>
|
||||
{user.email}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<tr key={'recent-user-table-entry-' + user.id}>
|
||||
<td>
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={tooltip}
|
||||
>
|
||||
<time>
|
||||
{user.username}
|
||||
</time>
|
||||
</OverlayTrigger>
|
||||
</td>
|
||||
<td>{Utils.displayDateTime(user.last_activity_at)}</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
recentActiveUser = (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count recent-active-users'>
|
||||
<div className='title'>{'Recent Active Users'}</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let newUsers;
|
||||
if (this.props.newlyCreatedUsers != null) {
|
||||
let content;
|
||||
if (this.props.newlyCreatedUsers.length === 0) {
|
||||
content = 'Loading...';
|
||||
} else {
|
||||
content = (
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
this.props.newlyCreatedUsers.map((user) => {
|
||||
const tooltip = (
|
||||
<Tooltip id={'new-user-email-tooltip-' + user.id}>
|
||||
{user.email}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<tr key={'new-user-table-entry-' + user.id}>
|
||||
<td>
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={tooltip}
|
||||
>
|
||||
<time>
|
||||
{user.username}
|
||||
</time>
|
||||
</OverlayTrigger>
|
||||
</td>
|
||||
<td>{Utils.displayDateTime(user.create_at)}</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
newUsers = (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count recent-active-users'>
|
||||
<div className='title'>{'Newly Created Users'}</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed team_statistics'>
|
||||
<h3>{'Statistics for ' + this.props.title}</h3>
|
||||
{serverError}
|
||||
<div className='row'>
|
||||
{totalCount}
|
||||
{postCount}
|
||||
{openChannelCount}
|
||||
{openPrivateCount}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{postCountsByDay}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{usersWithPostsByDay}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{recentActiveUser}
|
||||
{newUsers}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Analytics.defaultProps = {
|
||||
title: null,
|
||||
channelOpenCount: null,
|
||||
channelPrivateCount: null,
|
||||
postCount: null,
|
||||
postCountsDay: null,
|
||||
userCountsWithPostsDay: null,
|
||||
recentActiveUsers: null,
|
||||
newlyCreatedUsers: null,
|
||||
uniqueUserCount: null,
|
||||
serverError: null
|
||||
};
|
||||
|
||||
Analytics.propTypes = {
|
||||
title: React.PropTypes.string,
|
||||
channelOpenCount: React.PropTypes.number,
|
||||
channelPrivateCount: React.PropTypes.number,
|
||||
postCount: React.PropTypes.number,
|
||||
postCountsDay: React.PropTypes.object,
|
||||
userCountsWithPostsDay: React.PropTypes.object,
|
||||
recentActiveUsers: React.PropTypes.array,
|
||||
newlyCreatedUsers: React.PropTypes.array,
|
||||
uniqueUserCount: React.PropTypes.number,
|
||||
serverError: React.PropTypes.string
|
||||
};
|
||||
161
web/react/components/admin_console/system_analytics.jsx
Обычный файл
161
web/react/components/admin_console/system_analytics.jsx
Обычный файл
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Analytics from './analytics.jsx';
|
||||
import * as Client from '../../utils/client.jsx';
|
||||
|
||||
export default class SystemAnalytics extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.getData = this.getData.bind(this);
|
||||
|
||||
this.state = { // most of this state should be from a store in the future
|
||||
users: null,
|
||||
serverError: null,
|
||||
channel_open_count: null,
|
||||
channel_private_count: null,
|
||||
post_count: null,
|
||||
post_counts_day: null,
|
||||
user_counts_with_posts_day: null,
|
||||
recent_active_users: null,
|
||||
newly_created_users: null,
|
||||
unique_user_count: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getData() { // should be moved to an action creator eventually
|
||||
Client.getSystemAnalytics(
|
||||
'standard',
|
||||
(data) => {
|
||||
for (var index in data) {
|
||||
if (data[index].name === 'channel_open_count') {
|
||||
this.setState({channel_open_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'channel_private_count') {
|
||||
this.setState({channel_private_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'post_count') {
|
||||
this.setState({post_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'unique_user_count') {
|
||||
this.setState({unique_user_count: data[index].value});
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
|
||||
Client.getSystemAnalytics(
|
||||
'post_counts_day',
|
||||
(data) => {
|
||||
data.reverse();
|
||||
|
||||
var chartData = {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'Total Posts',
|
||||
fillColor: 'rgba(151,187,205,0.2)',
|
||||
strokeColor: 'rgba(151,187,205,1)',
|
||||
pointColor: 'rgba(151,187,205,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(151,187,205,1)',
|
||||
data: []
|
||||
}]
|
||||
};
|
||||
|
||||
for (var index in data) {
|
||||
if (data[index]) {
|
||||
var row = data[index];
|
||||
chartData.labels.push(row.name);
|
||||
chartData.datasets[0].data.push(row.value);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({post_counts_day: chartData});
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
|
||||
Client.getSystemAnalytics(
|
||||
'user_counts_with_posts_day',
|
||||
(data) => {
|
||||
data.reverse();
|
||||
|
||||
var chartData = {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'Active Users With Posts',
|
||||
fillColor: 'rgba(151,187,205,0.2)',
|
||||
strokeColor: 'rgba(151,187,205,1)',
|
||||
pointColor: 'rgba(151,187,205,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(151,187,205,1)',
|
||||
data: []
|
||||
}]
|
||||
};
|
||||
|
||||
for (var index in data) {
|
||||
if (data[index]) {
|
||||
var row = data[index];
|
||||
chartData.labels.push(row.name);
|
||||
chartData.datasets[0].data.push(row.value);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({user_counts_with_posts_day: chartData});
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
this.setState({
|
||||
serverError: null,
|
||||
channel_open_count: null,
|
||||
channel_private_count: null,
|
||||
post_count: null,
|
||||
post_counts_day: null,
|
||||
user_counts_with_posts_day: null,
|
||||
unique_user_count: null
|
||||
});
|
||||
|
||||
this.getData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Analytics
|
||||
title={'the System'}
|
||||
channelOpenCount={this.state.channel_open_count}
|
||||
channelPrivateCount={this.state.channel_private_count}
|
||||
postCount={this.state.post_count}
|
||||
postCountsDay={this.state.post_counts_day}
|
||||
userCountsWithPostsDay={this.state.user_counts_with_posts_day}
|
||||
uniqueUserCount={this.state.unique_user_count}
|
||||
serverError={this.state.serverError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SystemAnalytics.propTypes = {
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
@@ -1,13 +1,8 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Analytics from './analytics.jsx';
|
||||
import * as Client from '../../utils/client.jsx';
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
import LineChart from './line_chart.jsx';
|
||||
|
||||
var Tooltip = ReactBootstrap.Tooltip;
|
||||
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
||||
|
||||
export default class TeamAnalytics extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -15,7 +10,7 @@ export default class TeamAnalytics extends React.Component {
|
||||
|
||||
this.getData = this.getData.bind(this);
|
||||
|
||||
this.state = {
|
||||
this.state = { // most of this state should be from a store in the future
|
||||
users: null,
|
||||
serverError: null,
|
||||
channel_open_count: null,
|
||||
@@ -24,7 +19,8 @@ export default class TeamAnalytics extends React.Component {
|
||||
post_counts_day: null,
|
||||
user_counts_with_posts_day: null,
|
||||
recent_active_users: null,
|
||||
newly_created_users: null
|
||||
newly_created_users: null,
|
||||
unique_user_count: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,8 +28,8 @@ export default class TeamAnalytics extends React.Component {
|
||||
this.getData(this.props.team.id);
|
||||
}
|
||||
|
||||
getData(teamId) {
|
||||
Client.getAnalytics(
|
||||
getData(teamId) { // should be moved to an action creator eventually
|
||||
Client.getTeamAnalytics(
|
||||
teamId,
|
||||
'standard',
|
||||
(data) => {
|
||||
@@ -49,6 +45,10 @@ export default class TeamAnalytics extends React.Component {
|
||||
if (data[index].name === 'post_count') {
|
||||
this.setState({post_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'unique_user_count') {
|
||||
this.setState({unique_user_count: data[index].value});
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
@@ -56,7 +56,7 @@ export default class TeamAnalytics extends React.Component {
|
||||
}
|
||||
);
|
||||
|
||||
Client.getAnalytics(
|
||||
Client.getTeamAnalytics(
|
||||
teamId,
|
||||
'post_counts_day',
|
||||
(data) => {
|
||||
@@ -91,7 +91,7 @@ export default class TeamAnalytics extends React.Component {
|
||||
}
|
||||
);
|
||||
|
||||
Client.getAnalytics(
|
||||
Client.getTeamAnalytics(
|
||||
teamId,
|
||||
'user_counts_with_posts_day',
|
||||
(data) => {
|
||||
@@ -152,6 +152,10 @@ export default class TeamAnalytics extends React.Component {
|
||||
|
||||
var recentActive = [];
|
||||
for (let i = 0; i < usersList.length; i++) {
|
||||
if (usersList[i].last_activity_at == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recentActive.push(usersList[i]);
|
||||
if (i > 19) {
|
||||
break;
|
||||
@@ -198,227 +202,29 @@ export default class TeamAnalytics extends React.Component {
|
||||
post_counts_day: null,
|
||||
user_counts_with_posts_day: null,
|
||||
recent_active_users: null,
|
||||
newly_created_users: null
|
||||
newly_created_users: null,
|
||||
unique_user_count: null
|
||||
});
|
||||
|
||||
this.getData(newProps.team.id);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
}
|
||||
|
||||
render() {
|
||||
var serverError = '';
|
||||
if (this.state.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||
}
|
||||
|
||||
var totalCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Total Users'}<i className='fa fa-users'/></div>
|
||||
<div className='content'>{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openChannelCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Public Channels'}<i className='fa fa-globe'/></div>
|
||||
<div className='content'>{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openPrivateCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Private Groups'}<i className='fa fa-lock'/></div>
|
||||
<div className='content'>{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var postCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>{'Total Posts'}<i className='fa fa-comment'/></div>
|
||||
<div className='content'>{this.state.post_count == null ? 'Loading...' : this.state.post_count}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Total Posts'}</div>
|
||||
<div className='content'>{'Loading...'}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.state.post_counts_day != null) {
|
||||
postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Total Posts'}</div>
|
||||
<div className='content'>
|
||||
<LineChart
|
||||
data={this.state.post_counts_day}
|
||||
width='740'
|
||||
height='225'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Total Posts'}</div>
|
||||
<div>{'Loading...'}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.state.user_counts_with_posts_day != null) {
|
||||
usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>{'Active Users With Posts'}</div>
|
||||
<div className='content'>
|
||||
<LineChart
|
||||
data={this.state.user_counts_with_posts_day}
|
||||
width='740'
|
||||
height='225'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var recentActiveUser = (
|
||||
<div className='recent-active-users'>
|
||||
<div>{'Recent Active Users'}</div>
|
||||
<div>{'Loading...'}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.state.recent_active_users != null) {
|
||||
recentActiveUser = (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count recent-active-users'>
|
||||
<div className='title'>{'Recent Active Users'}</div>
|
||||
<div className='content'>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
this.state.recent_active_users.map((user) => {
|
||||
const tooltip = (
|
||||
<Tooltip id={'recent-user-email-tooltip-' + user.id}>
|
||||
{user.email}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<tr key={'recent-user-table-entry-' + user.id}>
|
||||
<td>
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={tooltip}
|
||||
>
|
||||
<time>
|
||||
{user.username}
|
||||
</time>
|
||||
</OverlayTrigger>
|
||||
</td>
|
||||
<td>{Utils.displayDateTime(user.last_activity_at)}</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var newUsers = (
|
||||
<div className='recent-active-users'>
|
||||
<div>{'Newly Created Users'}</div>
|
||||
<div>{'Loading...'}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.state.newly_created_users != null) {
|
||||
newUsers = (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count recent-active-users'>
|
||||
<div className='title'>{'Newly Created Users'}</div>
|
||||
<div className='content'>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
this.state.newly_created_users.map((user) => {
|
||||
const tooltip = (
|
||||
<Tooltip id={'new-user-email-tooltip-' + user.id}>
|
||||
{user.email}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<tr key={'new-user-table-entry-' + user.id}>
|
||||
<td>
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={tooltip}
|
||||
>
|
||||
<time>
|
||||
{user.username}
|
||||
</time>
|
||||
</OverlayTrigger>
|
||||
</td>
|
||||
<td>{Utils.displayDateTime(user.create_at)}</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed team_statistics'>
|
||||
<h3>{'Statistics for ' + this.props.team.name}</h3>
|
||||
{serverError}
|
||||
<div className='row'>
|
||||
{totalCount}
|
||||
{postCount}
|
||||
{openChannelCount}
|
||||
{openPrivateCount}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{postCountsByDay}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{usersWithPostsByDay}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{recentActiveUser}
|
||||
{newUsers}
|
||||
</div>
|
||||
<div>
|
||||
<Analytics
|
||||
title={this.props.team.name}
|
||||
users={this.state.users}
|
||||
channelOpenCount={this.state.channel_open_count}
|
||||
channelPrivateCount={this.state.channel_private_count}
|
||||
postCount={this.state.post_count}
|
||||
postCountsDay={this.state.post_counts_day}
|
||||
userCountsWithPostsDay={this.state.user_counts_with_posts_day}
|
||||
recentActiveUsers={this.state.recent_active_users}
|
||||
newlyCreatedUsers={this.state.newly_created_users}
|
||||
uniqueUserCount={this.state.unique_user_count}
|
||||
serverError={this.state.serverError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default class DeletePostModal extends React.Component {
|
||||
this.selectedList = null;
|
||||
|
||||
this.state = {
|
||||
show: true,
|
||||
show: false,
|
||||
post: null,
|
||||
commentCount: 0,
|
||||
error: ''
|
||||
@@ -40,6 +40,14 @@ export default class DeletePostModal extends React.Component {
|
||||
ModalStore.removeModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.state.show && !prevState.show) {
|
||||
setTimeout(() => {
|
||||
$(ReactDOM.findDOMNode(this.refs.deletePostBtn)).focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
handleDelete() {
|
||||
Client.deletePost(
|
||||
this.state.post.channel_id,
|
||||
@@ -149,10 +157,10 @@ export default class DeletePostModal extends React.Component {
|
||||
{'Cancel'}
|
||||
</button>
|
||||
<button
|
||||
ref='deletePostBtn'
|
||||
type='button'
|
||||
className='btn btn-danger'
|
||||
onClick={this.handleDelete}
|
||||
autoFocus='autofocus'
|
||||
>
|
||||
{'Delete'}
|
||||
</button>
|
||||
|
||||
@@ -5,11 +5,16 @@ import * as Utils from '../utils/utils.jsx';
|
||||
|
||||
export default function FileInfoPreview({filename, fileUrl, fileInfo}) {
|
||||
// non-image files include a section providing details about the file
|
||||
let infoString = 'File type ' + fileInfo.extension.toUpperCase();
|
||||
if (fileInfo.size > 0) {
|
||||
infoString += ', Size ' + Utils.fileSizeToString(fileInfo.size);
|
||||
const infoParts = [];
|
||||
|
||||
if (fileInfo.extension !== '') {
|
||||
infoParts.push('File type ' + fileInfo.extension.toUpperCase());
|
||||
}
|
||||
|
||||
infoParts.push('Size ' + Utils.fileSizeToString(fileInfo.size));
|
||||
|
||||
const infoString = infoParts.join(', ');
|
||||
|
||||
const name = decodeURIComponent(Utils.getFileName(filename));
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,8 @@ import SuggestionStore from '../../stores/suggestion_store.jsx';
|
||||
import UserStore from '../../stores/user_store.jsx';
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
|
||||
const MaxUserSuggestions = 40;
|
||||
|
||||
class AtMentionSuggestion extends React.Component {
|
||||
render() {
|
||||
const {item, isSelection, onClick} = this.props;
|
||||
@@ -78,6 +80,10 @@ export default class AtMentionProvider {
|
||||
if (user.username.startsWith(usernamePrefix)) {
|
||||
filtered.push(user);
|
||||
}
|
||||
|
||||
if (filtered.length >= MaxUserSuggestions) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add dummy users to represent the @all and @channel special mentions
|
||||
|
||||
@@ -59,7 +59,13 @@ export default class TeamSignupWelcomePage extends React.Component {
|
||||
}
|
||||
}.bind(this),
|
||||
function error(err) {
|
||||
this.setState({serverError: err.message});
|
||||
let errorMsg = err.message;
|
||||
|
||||
if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) {
|
||||
errorMsg = 'Please enter a valid email address';
|
||||
}
|
||||
|
||||
this.setState({emailError: '', serverError: errorMsg});
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,25 +4,68 @@
|
||||
import ErrorBar from '../components/error_bar.jsx';
|
||||
import SelectTeamModal from '../components/admin_console/select_team_modal.jsx';
|
||||
import AdminController from '../components/admin_console/admin_controller.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
export function setupAdminConsolePage(props) {
|
||||
ReactDOM.render(
|
||||
<AdminController
|
||||
tab={props.ActiveTab}
|
||||
teamId={props.TeamId}
|
||||
/>,
|
||||
document.getElementById('admin_controller')
|
||||
);
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
ReactDOM.render(
|
||||
<SelectTeamModal />,
|
||||
document.getElementById('select_team_modal')
|
||||
);
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<ErrorBar/>,
|
||||
document.getElementById('error_bar')
|
||||
);
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<div>
|
||||
<ErrorBar/>
|
||||
<AdminController
|
||||
tab={this.props.map.ActiveTab}
|
||||
teamId={this.props.map.TeamId}
|
||||
/>
|
||||
<SelectTeamModal />
|
||||
</div>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setup_admin_console_page = setupAdminConsolePage;
|
||||
global.window.setup_admin_console_page = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<Root map={props} />,
|
||||
document.getElementById('admin_controller')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,20 +2,69 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import Authorize from '../components/authorize.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
function setupAuthorizePage(props) {
|
||||
ReactDOM.render(
|
||||
<Authorize
|
||||
teamName={props.TeamName}
|
||||
appName={props.AppName}
|
||||
responseType={props.ResponseType}
|
||||
clientId={props.ClientId}
|
||||
redirectUri={props.RedirectUri}
|
||||
scope={props.Scope}
|
||||
state={props.State}
|
||||
/>,
|
||||
document.getElementById('authorize')
|
||||
);
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<Authorize
|
||||
teamName={this.props.map.TeamName}
|
||||
appName={this.props.map.AppName}
|
||||
responseType={this.props.map.ResponseType}
|
||||
clientId={this.props.map.ClientId}
|
||||
redirectUri={this.props.map.RedirectUri}
|
||||
scope={this.props.map.Scope}
|
||||
state={this.props.map.State}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setup_authorize_page = setupAuthorizePage;
|
||||
global.window.setup_authorize_page = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<Root map={props} />,
|
||||
document.getElementById('authorize')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,18 +2,67 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import ClaimAccount from '../components/claim/claim_account.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
function setupClaimAccountPage(props) {
|
||||
ReactDOM.render(
|
||||
<ClaimAccount
|
||||
email={props.Email}
|
||||
currentType={props.CurrentType}
|
||||
newType={props.NewType}
|
||||
teamName={props.TeamName}
|
||||
teamDisplayName={props.TeamDisplayName}
|
||||
/>,
|
||||
document.getElementById('claim')
|
||||
);
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<ClaimAccount
|
||||
email={this.props.map.Email}
|
||||
currentType={this.props.map.CurrentType}
|
||||
newType={this.props.map.NewType}
|
||||
teamName={this.props.map.TeamName}
|
||||
teamDisplayName={this.props.map.TeamDisplayName}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setup_claim_account_page = setupClaimAccountPage;
|
||||
global.window.setup_claim_account_page = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<Root map={props} />,
|
||||
document.getElementById('claim')
|
||||
);
|
||||
};
|
||||
@@ -2,15 +2,63 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import Docs from '../components/docs.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
function setupDocumentationPage(props) {
|
||||
ReactDOM.render(
|
||||
<Docs
|
||||
site={props.Site}
|
||||
/>,
|
||||
document.getElementById('docs')
|
||||
);
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<Docs site={this.props.map.Site} />
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.mm_user = global.window.mm_user || {};
|
||||
global.window.setup_documentation_page = setupDocumentationPage;
|
||||
|
||||
global.window.setup_documentation_page = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<Root map={props} />,
|
||||
document.getElementById('docs')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,18 +2,67 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import PasswordReset from '../components/password_reset.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
function setupPasswordResetPage(props) {
|
||||
ReactDOM.render(
|
||||
<PasswordReset
|
||||
isReset={props.IsReset}
|
||||
teamDisplayName={props.TeamDisplayName}
|
||||
teamName={props.TeamName}
|
||||
hash={props.Hash}
|
||||
data={props.Data}
|
||||
/>,
|
||||
document.getElementById('reset')
|
||||
);
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<PasswordReset
|
||||
isReset={this.props.map.IsReset}
|
||||
teamDisplayName={this.props.map.TeamDisplayName}
|
||||
teamName={this.props.map.TeamName}
|
||||
hash={this.props.map.Hash}
|
||||
data={this.props.map.Data}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setup_password_reset_page = setupPasswordResetPage;
|
||||
global.window.setup_password_reset_page = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<Root map={props} />,
|
||||
document.getElementById('reset')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,8 +2,60 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import SignupTeam from '../components/signup_team.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
function setupSignupTeamPage(props) {
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired,
|
||||
teams: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<SignupTeam teams={this.props.teams} />
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setup_signup_team_page = function setup(props) {
|
||||
var teams = [];
|
||||
|
||||
for (var prop in props) {
|
||||
@@ -15,9 +67,10 @@ function setupSignupTeamPage(props) {
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<SignupTeam teams={teams} />,
|
||||
<Root
|
||||
map={props}
|
||||
teams={teams}
|
||||
/>,
|
||||
document.getElementById('signup-team')
|
||||
);
|
||||
}
|
||||
|
||||
global.window.setup_signup_team_page = setupSignupTeamPage;
|
||||
};
|
||||
@@ -2,16 +2,65 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import SignupTeamComplete from '../components/signup_team_complete.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
function setupSignupTeamCompletePage(props) {
|
||||
ReactDOM.render(
|
||||
<SignupTeamComplete
|
||||
email={props.Email}
|
||||
hash={props.Hash}
|
||||
data={props.Data}
|
||||
/>,
|
||||
document.getElementById('signup-team-complete')
|
||||
);
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<SignupTeamComplete
|
||||
email={this.props.map.Email}
|
||||
hash={this.props.map.Hash}
|
||||
data={this.props.map.Data}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setup_signup_team_complete_page = setupSignupTeamCompletePage;
|
||||
global.window.setup_signup_team_complete_page = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<Root map={props} />,
|
||||
document.getElementById('signup-team-complete')
|
||||
);
|
||||
};
|
||||
@@ -2,19 +2,68 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import SignupUserComplete from '../components/signup_user_complete.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
function setupSignupUserCompletePage(props) {
|
||||
ReactDOM.render(
|
||||
<SignupUserComplete
|
||||
teamId={props.TeamId}
|
||||
teamName={props.TeamName}
|
||||
teamDisplayName={props.TeamDisplayName}
|
||||
email={props.Email}
|
||||
hash={props.Hash}
|
||||
data={props.Data}
|
||||
/>,
|
||||
document.getElementById('signup-user-complete')
|
||||
);
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<SignupUserComplete
|
||||
teamId={this.props.map.TeamId}
|
||||
teamName={this.props.map.TeamName}
|
||||
teamDisplayName={this.props.map.TeamDisplayName}
|
||||
email={this.props.map.Email}
|
||||
hash={this.props.map.Hash}
|
||||
data={this.props.map.Data}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setup_signup_user_complete_page = setupSignupUserCompletePage;
|
||||
global.window.setup_signup_user_complete_page = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<Root map={props} />,
|
||||
document.getElementById('signup-user-complete')
|
||||
);
|
||||
};
|
||||
@@ -2,15 +2,66 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import EmailVerify from '../components/email_verify.jsx';
|
||||
import * as Client from '../utils/client.jsx';
|
||||
|
||||
global.window.setupVerifyPage = function setupVerifyPage(props) {
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translations: null,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
map: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Client.getTranslations(
|
||||
this.props.map.Locale,
|
||||
(data) => {
|
||||
this.setState({
|
||||
translations: data,
|
||||
loaded: true
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.loaded) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={this.props.map.Locale}
|
||||
messages={this.state.translations}
|
||||
>
|
||||
<EmailVerify
|
||||
isVerified={this.props.map.IsVerified}
|
||||
teamURL={this.props.map.TeamURL}
|
||||
userEmail={this.props.map.UserEmail}
|
||||
resendSuccess={this.props.map.ResendSuccess}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
global.window.setupVerifyPage = function setup(props) {
|
||||
ReactDOM.render(
|
||||
<EmailVerify
|
||||
isVerified={props.IsVerified}
|
||||
teamURL={props.TeamURL}
|
||||
userEmail={props.UserEmail}
|
||||
resendSuccess={props.ResendSuccess}
|
||||
/>,
|
||||
<Root map={props} />,
|
||||
document.getElementById('verify')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -399,7 +399,7 @@ export function getConfig(success, error) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getAnalytics(teamId, name, success, error) {
|
||||
export function getTeamAnalytics(teamId, name, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/analytics/' + teamId + '/' + name,
|
||||
dataType: 'json',
|
||||
@@ -407,7 +407,21 @@ export function getAnalytics(teamId, name, success, error) {
|
||||
type: 'GET',
|
||||
success,
|
||||
error: (xhr, status, err) => {
|
||||
var e = handleError('getAnalytics', xhr, status, err);
|
||||
var e = handleError('getTeamAnalytics', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getSystemAnalytics(name, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/analytics/' + name,
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success,
|
||||
error: (xhr, status, err) => {
|
||||
var e = handleError('getSystemAnalytics', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
<body>
|
||||
<script src="/static/js/Chart.min.js"></script>
|
||||
|
||||
<div id='error_bar'></div>
|
||||
|
||||
<div id='admin_controller' class='container-fluid'></div>
|
||||
|
||||
<div id='select_team_modal'></div>
|
||||
<div id='admin_controller'></div>
|
||||
|
||||
<script>
|
||||
window.setup_admin_console_page({{ .Props }});
|
||||
|
||||
@@ -565,9 +565,9 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
user := result.Data.(*model.User)
|
||||
|
||||
if user.LastActivityAt > 0 {
|
||||
api.SendEmailChangeVerifyEmailAndForget(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
|
||||
api.SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
|
||||
} else {
|
||||
api.SendVerifyEmailAndForget(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
|
||||
api.SendVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
|
||||
}
|
||||
|
||||
newAddress := strings.Replace(r.URL.String(), "&resend=true", "&resend_success=true", -1)
|
||||
|
||||
Ссылка в новой задаче
Block a user