mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-42713] - Remove cloud user limit restrictions (#19835)
* [MM-42713] - Remove cloud user limit restrictions * remove unused code * fix translations * feedback impl-1 * enterprise code clean up * feedback impl-2 * fix mocks * fix translations Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
@@ -36,9 +36,6 @@ func (api *API) InitCloud() {
|
||||
api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(getSubscription)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/invoices", api.APISessionRequired(getInvoicesForSubscription)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf", api.APISessionRequired(getSubscriptionInvoicePDF)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/limitreached/invite", api.APISessionRequired(sendAdminUpgradeRequestEmail)).Methods("POST")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/limitreached/join", api.APIHandler(sendAdminUpgradeRequestEmailOnJoin)).Methods("POST")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription/stats", api.APISessionRequired(getSubscriptionStats)).Methods("GET")
|
||||
api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(changeSubscription)).Methods("PUT")
|
||||
|
||||
// POST /api/v4/cloud/webhook
|
||||
@@ -115,18 +112,6 @@ func changeSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func getSubscriptionStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
s, err := c.App.GetSubscriptionStats()
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
stats, _ := json.Marshal(s)
|
||||
|
||||
w.Write([]byte(string(stats)))
|
||||
}
|
||||
|
||||
func getCloudProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
|
||||
c.Err = model.NewAppError("Api4.getCloudProducts", "api.cloud.license_error", nil, "", http.StatusNotImplemented)
|
||||
@@ -464,49 +449,3 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func sendAdminUpgradeRequestEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
|
||||
c.Err = model.NewAppError("Api4.sendAdminUpgradeRequestEmail", "api.cloud.license_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
user, appErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.sendAdminUpgradeRequestEmail", appErr.Id, nil, appErr.Error(), appErr.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
sub, err := c.App.Cloud().GetSubscription(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.sendAdminUpgradeRequestEmail", "api.cloud.request_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if appErr = c.App.SendAdminUpgradeRequestEmail(user.Username, sub, model.InviteLimitation); appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.sendAdminUpgradeRequestEmail", appErr.Id, nil, appErr.Error(), appErr.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func sendAdminUpgradeRequestEmailOnJoin(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
|
||||
c.Err = model.NewAppError("Api4.sendAdminUpgradeRequestEmailOnJoin", "api.cloud.license_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
sub, err := c.App.Cloud().GetSubscription("")
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("Api4.sendAdminUpgradeRequestEmailOnJoin", "api.cloud.request_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if appErr := c.App.SendAdminUpgradeRequestEmail("", sub, model.JoinLimitation); appErr != nil {
|
||||
c.Err = model.NewAppError("Api4.sendAdminUpgradeRequestEmail", appErr.Id, nil, appErr.Error(), appErr.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
44
api4/team.go
44
api4/team.go
@@ -1293,34 +1293,12 @@ func inviteUsersToTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.AddMeta("emails", emailList)
|
||||
|
||||
if graceful {
|
||||
cloudUserLimit := *c.App.Config().ExperimentalSettings.CloudUserLimit
|
||||
var invitesOverLimit []*model.EmailInviteWithError
|
||||
if c.App.Channels().License() != nil && *c.App.Channels().License().Features.Cloud && cloudUserLimit > 0 {
|
||||
subscription, subErr := c.App.Cloud().GetSubscription(c.AppContext.Session().UserId)
|
||||
if subErr != nil {
|
||||
c.Err = model.NewAppError(
|
||||
"Api4.inviteUsersToTeam",
|
||||
"api.team.cloud.subscription.error",
|
||||
nil,
|
||||
subErr.Error(),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if subscription == nil || subscription.IsPaidTier != "true" {
|
||||
emailList, invitesOverLimit, _ = c.App.GetErrorListForEmailsOverLimit(emailList, cloudUserLimit)
|
||||
}
|
||||
}
|
||||
|
||||
var invitesWithError []*model.EmailInviteWithError
|
||||
var err *model.AppError
|
||||
if emailList != nil {
|
||||
invitesWithError, err = c.App.InviteNewUsersToTeamGracefully(emailList, c.Params.TeamId, c.AppContext.Session().UserId, "")
|
||||
}
|
||||
|
||||
if len(invitesOverLimit) > 0 {
|
||||
invitesWithError = append(invitesWithError, invitesOverLimit...)
|
||||
}
|
||||
|
||||
if invitesWithError != nil {
|
||||
errList := make([]string, 0, len(invitesWithError))
|
||||
for _, inv := range invitesWithError {
|
||||
@@ -1414,24 +1392,6 @@ func inviteGuestsToChannels(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
auditRec.AddMeta("channels", guestsInvite.Channels)
|
||||
|
||||
if graceful {
|
||||
cloudUserLimit := *c.App.Config().ExperimentalSettings.CloudUserLimit
|
||||
var invitesOverLimit []*model.EmailInviteWithError
|
||||
if c.App.Channels().License() != nil && *c.App.Channels().License().Features.Cloud && cloudUserLimit > 0 && c.IsSystemAdmin() {
|
||||
subscription, err := c.App.Cloud().GetSubscription(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(
|
||||
"Api4.inviteGuestsToChannel",
|
||||
"api.team.cloud.subscription.error",
|
||||
nil,
|
||||
err.Error(),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if subscription == nil || subscription.IsPaidTier != "true" {
|
||||
guestsInvite.Emails, invitesOverLimit, _ = c.App.GetErrorListForEmailsOverLimit(guestsInvite.Emails, cloudUserLimit)
|
||||
}
|
||||
}
|
||||
|
||||
var invitesWithError []*model.EmailInviteWithError
|
||||
var err *model.AppError
|
||||
|
||||
@@ -1439,10 +1399,6 @@ func inviteGuestsToChannels(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
invitesWithError, err = c.App.InviteGuestsToChannelsGracefully(c.Params.TeamId, &guestsInvite, c.AppContext.Session().UserId)
|
||||
}
|
||||
|
||||
if len(invitesOverLimit) > 0 {
|
||||
invitesWithError = append(invitesWithError, invitesOverLimit...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errList := make([]string, 0, len(invitesWithError))
|
||||
for _, inv := range invitesWithError {
|
||||
|
||||
30
api4/user.go
30
api4/user.go
@@ -159,15 +159,6 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// New user created, check cloud limits and send emails if needed
|
||||
// Soft fail on error since user is already created
|
||||
if ruser != nil {
|
||||
err = c.App.CheckAndSendUserLimitWarningEmails(c.AppContext)
|
||||
if err != nil {
|
||||
c.LogErrorByCode(err)
|
||||
}
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("user", ruser) // overwrite meta
|
||||
|
||||
@@ -1468,18 +1459,6 @@ func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// if non cloud instances, isOverLimit is false and no error
|
||||
isAtLimit, err := c.App.CheckCloudAccountAtLimit()
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("updateUserActive", "api.user.update_active.cloud_at_limit_check_error", nil, "userId="+c.Params.UserId, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if active && isAtLimit {
|
||||
c.Err = model.NewAppError("updateUserActive", "api.user.update_active.cloud_at_or_over_limit_check_overcapacity", nil, "userId="+c.Params.UserId, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = c.App.UpdateActive(c.AppContext, user, active); err != nil {
|
||||
c.Err = err
|
||||
}
|
||||
@@ -1498,15 +1477,6 @@ func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventUserActivationStatusChange, "", "", "", nil)
|
||||
c.App.Publish(message)
|
||||
|
||||
// If activating, run cloud check for limit overages
|
||||
if active {
|
||||
emailErr := c.App.CheckAndSendUserLimitWarningEmails(c.AppContext)
|
||||
if emailErr != nil {
|
||||
c.Err = emailErr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
|
||||
@@ -2183,46 +2183,6 @@ func assertWebsocketEventUserUpdatedWithEmail(t *testing.T, client *model.WebSoc
|
||||
}
|
||||
|
||||
func TestUpdateUserActive(t *testing.T) {
|
||||
t.Run("not activating more users when cloud license users at limit", func(t *testing.T) {
|
||||
// create 5 active users
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cloudMock := &mocks.CloudInterface{}
|
||||
cloudMock.Mock.On(
|
||||
"GetSubscription", mock.Anything,
|
||||
).Return(&model.Subscription{
|
||||
ID: "MySubscriptionID",
|
||||
CustomerID: "MyCustomer",
|
||||
ProductID: "SomeProductId",
|
||||
AddOns: []string{},
|
||||
StartAt: 1000000000,
|
||||
EndAt: 2000000000,
|
||||
CreateAt: 1000000000,
|
||||
Seats: 100,
|
||||
DNS: "some.dns.server",
|
||||
IsPaidTier: "false",
|
||||
}, nil)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
th.App.Srv().Cloud = cloudMock
|
||||
|
||||
user := th.BasicUser
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.TeamSettings.EnableUserDeactivation = true
|
||||
*cfg.ExperimentalSettings.CloudUserLimit = 4
|
||||
})
|
||||
|
||||
// deactivate 5th user, now we have 4 active users and are at limit
|
||||
_, err := th.SystemAdminClient.UpdateUserActive(user.Id, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// try and reactivate 5th user, not allowed because it exceeds the set cloud user limit
|
||||
resp, err := th.SystemAdminClient.UpdateUserActive(user.Id, true)
|
||||
CheckErrorMessage(t, err, "Unable to activate more users as the cloud account is over capacity.")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
t.Run("basic tests", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -269,9 +269,6 @@ type AppIface interface {
|
||||
SearchAllChannels(term string, opts model.ChannelSearchOpts) (model.ChannelListWithTeamData, int64, *model.AppError)
|
||||
// SearchAllTeams returns a team list and the total count of the results
|
||||
SearchAllTeams(searchOpts *model.TeamSearch) ([]*model.Team, int64, *model.AppError)
|
||||
// SendAdminUpgradeRequestEmail takes the username of user trying to alert admins and then applies rate limit of n (number of admins) emails per user per day
|
||||
// before sending the emails.
|
||||
SendAdminUpgradeRequestEmail(username string, subscription *model.Subscription, action string) *model.AppError
|
||||
// SendNoCardPaymentFailedEmail
|
||||
SendNoCardPaymentFailedEmail() *model.AppError
|
||||
// SessionHasPermissionToManageBot returns nil if the session has access to manage the given bot.
|
||||
@@ -411,9 +408,7 @@ type AppIface interface {
|
||||
CancelJob(jobId string) *model.AppError
|
||||
ChannelMembersToRemove(teamID *string) ([]*model.ChannelMember, *model.AppError)
|
||||
Channels() *Channels
|
||||
CheckAndSendUserLimitWarningEmails(c *request.Context) *model.AppError
|
||||
CheckCanInviteToSharedChannel(channelId string) error
|
||||
CheckCloudAccountAtLimit() (bool, *model.AppError)
|
||||
CheckForClientSideCert(r *http.Request) (string, string, string)
|
||||
CheckIntegrity() <-chan model.IntegrityCheckResult
|
||||
CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError
|
||||
@@ -598,7 +593,6 @@ type AppIface interface {
|
||||
GetEmojiByName(emojiName string) (*model.Emoji, *model.AppError)
|
||||
GetEmojiImage(emojiId string) ([]byte, string, *model.AppError)
|
||||
GetEmojiList(page, perPage int, sort string) ([]*model.Emoji, *model.AppError)
|
||||
GetErrorListForEmailsOverLimit(emailList []string, cloudUserLimit int64) ([]string, []*model.EmailInviteWithError, *model.AppError)
|
||||
GetFile(fileID string) ([]byte, *model.AppError)
|
||||
GetFileInfo(fileID string) (*model.FileInfo, *model.AppError)
|
||||
GetFileInfos(page, perPage int, opt *model.GetFileInfosOptions) ([]*model.FileInfo, *model.AppError)
|
||||
@@ -733,7 +727,6 @@ type AppIface interface {
|
||||
GetStatus(userID string) (*model.Status, *model.AppError)
|
||||
GetStatusFromCache(userID string) *model.Status
|
||||
GetStatusesByIds(userIDs []string) (map[string]interface{}, *model.AppError)
|
||||
GetSubscriptionStats() (*model.SubscriptionStats, *model.AppError)
|
||||
GetSystemBot() (*model.Bot, *model.AppError)
|
||||
GetTeam(teamID string) (*model.Team, *model.AppError)
|
||||
GetTeamByInviteId(inviteId string) (*model.Team, *model.AppError)
|
||||
|
||||
172
app/cloud.go
172
app/cloud.go
@@ -4,11 +4,8 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
@@ -23,175 +20,6 @@ func (a *App) getSysAdminsEmailRecipients() ([]*model.User, *model.AppError) {
|
||||
return a.GetUsers(userOptions)
|
||||
}
|
||||
|
||||
// SendAdminUpgradeRequestEmail takes the username of user trying to alert admins and then applies rate limit of n (number of admins) emails per user per day
|
||||
// before sending the emails.
|
||||
func (a *App) SendAdminUpgradeRequestEmail(username string, subscription *model.Subscription, action string) *model.AppError {
|
||||
if a.Srv().License() == nil || (a.Srv().License() != nil && !*a.Srv().License().Features.Cloud) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if subscription != nil && subscription.IsPaidTier == "true" {
|
||||
return nil
|
||||
}
|
||||
|
||||
year, month, day := time.Now().Date()
|
||||
key := fmt.Sprintf("%s-%d-%s-%d", action, day, month, year)
|
||||
|
||||
if a.Srv().EmailService.GetPerDayEmailRateLimiter() == nil {
|
||||
return model.NewAppError("app.SendAdminUpgradeRequestEmail", "app.email.no_rate_limiter.app_error", nil, fmt.Sprintf("for key=%s", key), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// rate limit based on combination of date and action as key
|
||||
rateLimited, result, err := a.Srv().EmailService.GetPerDayEmailRateLimiter().RateLimit(key, 1)
|
||||
if err != nil {
|
||||
return model.NewAppError("app.SendAdminUpgradeRequestEmail", "app.email.setup_rate_limiter.app_error", nil, fmt.Sprintf("for key=%s, error=%v", key, err), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if rateLimited {
|
||||
return model.NewAppError("app.SendAdminUpgradeRequestEmail",
|
||||
"app.email.rate_limit_exceeded.app_error", map[string]interface{}{"RetryAfter": result.RetryAfter.String(), "ResetAfter": result.ResetAfter.String()},
|
||||
fmt.Sprintf("key=%s, retry_after_secs=%f, reset_after_secs=%f",
|
||||
key, result.RetryAfter.Seconds(), result.ResetAfter.Seconds()),
|
||||
http.StatusRequestEntityTooLarge)
|
||||
}
|
||||
|
||||
sysAdmins, e := a.getSysAdminsEmailRecipients()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
// we want to at least have one email sent out to an admin
|
||||
countNotOks := 0
|
||||
|
||||
for admin := range sysAdmins {
|
||||
ok, err := a.Srv().EmailService.SendUpgradeEmail(username, sysAdmins[admin].Email, sysAdmins[admin].Locale, *a.Config().ServiceSettings.SiteURL, action)
|
||||
if !ok || err != nil {
|
||||
a.Log().Error("Error sending upgrade request email", mlog.Err(err))
|
||||
countNotOks++
|
||||
}
|
||||
}
|
||||
|
||||
// if not even one admin got an email, we consider that this operation errored
|
||||
if countNotOks == len(sysAdmins) {
|
||||
return model.NewAppError("app.SendAdminUpgradeRequestEmail", "app.user.send_emails.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) GetSubscriptionStats() (*model.SubscriptionStats, *model.AppError) {
|
||||
if a.Srv().License() == nil || !*a.Srv().License().Features.Cloud {
|
||||
return nil, model.NewAppError("app.GetSubscriptionStats", "api.cloud.license_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
subscription, appErr := a.Cloud().GetSubscription("")
|
||||
if appErr != nil {
|
||||
return nil, model.NewAppError("app.GetSubscriptionStats", "api.cloud.request_error", nil, appErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
count, err := a.Srv().Store.User().Count(model.UserCountOptions{})
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("app.GetSubscriptionStats", "app.user.get_total_users_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
cloudUserLimit := *a.Config().ExperimentalSettings.CloudUserLimit
|
||||
|
||||
s := cloudUserLimit - count
|
||||
|
||||
return &model.SubscriptionStats{
|
||||
RemainingSeats: int(s),
|
||||
IsPaidTier: subscription.IsPaidTier,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) CheckCloudAccountAtLimit() (bool, *model.AppError) {
|
||||
if a.Srv().License() == nil || (a.Srv().License() != nil && !*a.Srv().License().Features.Cloud) {
|
||||
// Not cloud instance, so no at limit checks
|
||||
return false, nil
|
||||
}
|
||||
|
||||
stats, err := a.GetSubscriptionStats()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if stats.IsPaidTier == "true" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if stats.RemainingSeats < 1 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (a *App) CheckAndSendUserLimitWarningEmails(c *request.Context) *model.AppError {
|
||||
if a.Srv().License() == nil || (a.Srv().License() != nil && !*a.Srv().License().Features.Cloud) {
|
||||
// Not cloud instance, do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
subscription, err := a.Cloud().GetSubscription(c.Session().UserId)
|
||||
if err != nil {
|
||||
return model.NewAppError(
|
||||
"app.CheckAndSendUserLimitWarningEmails",
|
||||
"api.cloud.get_subscription.error",
|
||||
nil,
|
||||
err.Error(),
|
||||
http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if subscription != nil && subscription.IsPaidTier == "true" {
|
||||
// Paid subscription, do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
cloudUserLimit := *a.Config().ExperimentalSettings.CloudUserLimit
|
||||
systemUserCount, _ := a.Srv().Store.User().Count(model.UserCountOptions{})
|
||||
remainingUsers := cloudUserLimit - systemUserCount
|
||||
|
||||
if remainingUsers > 0 {
|
||||
return nil
|
||||
}
|
||||
sysAdmins, appErr := a.getSysAdminsEmailRecipients()
|
||||
if appErr != nil {
|
||||
return model.NewAppError(
|
||||
"app.CheckAndSendUserLimitWarningEmails",
|
||||
"api.cloud.get_admins_emails.error",
|
||||
nil,
|
||||
appErr.Error(),
|
||||
http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// -1 means they are 1 user over the limit - we only want to send the email for the 11th user
|
||||
if remainingUsers == -1 {
|
||||
// Over limit by 1 user
|
||||
for admin := range sysAdmins {
|
||||
_, appErr := a.Srv().EmailService.SendOverUserLimitWarningEmail(sysAdmins[admin].Email, sysAdmins[admin].Locale, *a.Config().ServiceSettings.SiteURL)
|
||||
if appErr != nil {
|
||||
a.Log().Error(
|
||||
"Error sending user limit warning email to admin",
|
||||
mlog.String("username", sysAdmins[admin].Username),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if remainingUsers == 0 {
|
||||
// At limit
|
||||
for admin := range sysAdmins {
|
||||
_, err := a.Srv().EmailService.SendAtUserLimitWarningEmail(sysAdmins[admin].Email, sysAdmins[admin].Locale, *a.Config().ServiceSettings.SiteURL)
|
||||
if err != nil {
|
||||
a.Log().Error(
|
||||
"Error sending user limit warning email to admin",
|
||||
mlog.String("username", sysAdmins[admin].Username),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SendPaymentFailedEmail(failedPayment *model.FailedPayment) *model.AppError {
|
||||
sysAdmins, err := a.getSysAdminsEmailRecipients()
|
||||
if err != nil {
|
||||
|
||||
@@ -741,33 +741,6 @@ func (es *Service) CreateVerifyEmailToken(userID string, newEmail string) (*mode
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendAtUserLimitWarningEmail(email string, locale string, siteURL string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.at_limit_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.at_limit_title")
|
||||
data.Props["Info1"] = T("api.templates.at_limit_info1")
|
||||
data.Props["Info2"] = T("api.templates.at_limit_info2")
|
||||
data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("reached_user_limit_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendLicenseInactivityEmail(email, name, locale, siteURL string) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
subject := T("api.templates.server_inactivity_subject")
|
||||
@@ -829,229 +802,6 @@ func (es *Service) SendLicenseUpForRenewalEmail(email, name, locale, siteURL, re
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendUpgradeEmail formats an email template and sends an email to an admin specified in the email arg
|
||||
func (es *Service) SendUpgradeEmail(user, email, locale, siteURL, action string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.upgrade_request_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["Info5"] = T("api.templates.at_limit_info5")
|
||||
data.Props["BillingPath"] = "admin_console/billing/subscription"
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
if action == model.InviteLimitation {
|
||||
data.Props["Title"] = T("api.templates.upgrade_request_title", map[string]interface{}{"UserName": user})
|
||||
data.Props["Info4"] = T("api.templates.upgrade_request_info4")
|
||||
} else {
|
||||
data.Props["Title"] = T("api.templates.upgrade_request_title2")
|
||||
data.Props["Info4"] = T("api.templates.upgrade_request_info4_2")
|
||||
}
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("cloud_upgrade_request_email", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendOverUserLimitWarningEmail(email string, locale string, siteURL string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.over_limit_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.over_limit_title")
|
||||
data.Props["Info1"] = T("api.templates.over_limit_info1")
|
||||
data.Props["Info2"] = T("api.templates.over_limit_info2")
|
||||
data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("reached_user_limit_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendOverUserLimitThirtyDayWarningEmail(email string, locale string, siteURL string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.over_limit_30_days_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.over_limit_30_days_title")
|
||||
data.Props["Info1"] = T("api.templates.over_limit_30_days_info1")
|
||||
data.Props["Info2"] = T("api.templates.over_limit_30_days_info2")
|
||||
data.Props["Info2Item1"] = T("api.templates.over_limit_30_days_info2_item1")
|
||||
data.Props["Info2Item2"] = T("api.templates.over_limit_30_days_info2_item2")
|
||||
data.Props["Info2Item3"] = T("api.templates.over_limit_30_days_info2_item3")
|
||||
data.Props["Button"] = T("api.templates.over_limit_fix_now")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("over_user_limit_30_days_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendOverUserLimitNinetyDayWarningEmail(email string, locale string, siteURL string, overLimitDate string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.over_limit_90_days_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.over_limit_90_days_title")
|
||||
data.Props["Info1"] = T("api.templates.over_limit_90_days_info1", map[string]interface{}{"OverLimitDate": overLimitDate})
|
||||
data.Props["Info2"] = T("api.templates.over_limit_90_days_info2")
|
||||
data.Props["Info3"] = T("api.templates.over_limit_90_days_info3")
|
||||
data.Props["Info4"] = T("api.templates.over_limit_90_days_info4")
|
||||
data.Props["Button"] = T("api.templates.over_limit_fix_now")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("over_user_limit_90_days_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendOverUserLimitWorkspaceSuspendedWarningEmail(email string, locale string, siteURL string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.over_limit_suspended_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.over_limit_suspended_title")
|
||||
data.Props["Info1"] = T("api.templates.over_limit_suspended_info1")
|
||||
data.Props["Info2"] = T("api.templates.over_limit_suspended_info2")
|
||||
data.Props["Button"] = T("api.templates.over_limit_suspended_contact_support")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("over_user_limit_workspace_suspended_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendOverUserFourteenDayWarningEmail(email string, locale string, siteURL string, overLimitDate string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.over_limit_14_days_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.over_limit_14_days_title")
|
||||
data.Props["Info1"] = T("api.templates.over_limit_14_days_info1", map[string]interface{}{"OverLimitDate": overLimitDate})
|
||||
data.Props["Button"] = T("api.templates.over_limit_fix_now")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("over_user_limit_7_days_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendOverUserSevenDayWarningEmail(email string, locale string, siteURL string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.over_limit_7_days_subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.over_limit_7_days_title")
|
||||
data.Props["Info1"] = T("api.templates.over_limit_7_days_info1")
|
||||
data.Props["Button"] = T("api.templates.over_limit_fix_now")
|
||||
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
|
||||
|
||||
data.Props["Footer"] = T("api.templates.copyright")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("over_user_limit_7_days_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendSuspensionEmailToSupport(email string, installationID string, customerID string, subscriptionID string, siteURL string, userCount int64) (bool, error) {
|
||||
// Localization not needed
|
||||
|
||||
subject := fmt.Sprintf("Cloud Installation %s Scheduled Suspension", installationID)
|
||||
data := es.NewEmailTemplateData("en")
|
||||
data.Props["CustomerID"] = customerID
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["SubscriptionID"] = subscriptionID
|
||||
data.Props["InstallationID"] = installationID
|
||||
data.Props["SuspensionDate"] = time.Now().AddDate(0, 0, 61).Format("2006-01-02")
|
||||
data.Props["UserCount"] = userCount
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("over_user_limit_support_body", data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (es *Service) SendPaymentFailedEmail(email string, locale string, failedPayment *model.FailedPayment, siteURL string) (bool, error) {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
|
||||
@@ -111,27 +111,6 @@ func (_m *ServiceInterface) NewEmailTemplateData(locale string) templates.Data {
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendAtUserLimitWarningEmail provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendAtUserLimitWarningEmail(_a0 string, locale string, siteURL string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendChangeUsernameEmail provides a mock function with given fields: newUsername, _a1, locale, siteURL
|
||||
func (_m *ServiceInterface) SendChangeUsernameEmail(newUsername string, _a1 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(newUsername, _a1, locale, siteURL)
|
||||
@@ -342,132 +321,6 @@ func (_m *ServiceInterface) SendNotificationMail(to string, subject string, html
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendOverUserFourteenDayWarningEmail provides a mock function with given fields: _a0, locale, siteURL, overLimitDate
|
||||
func (_m *ServiceInterface) SendOverUserFourteenDayWarningEmail(_a0 string, locale string, siteURL string, overLimitDate string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, siteURL, overLimitDate)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, siteURL, overLimitDate)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, siteURL, overLimitDate)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendOverUserLimitNinetyDayWarningEmail provides a mock function with given fields: _a0, locale, siteURL, overLimitDate
|
||||
func (_m *ServiceInterface) SendOverUserLimitNinetyDayWarningEmail(_a0 string, locale string, siteURL string, overLimitDate string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, siteURL, overLimitDate)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, siteURL, overLimitDate)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, siteURL, overLimitDate)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendOverUserLimitThirtyDayWarningEmail provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendOverUserLimitThirtyDayWarningEmail(_a0 string, locale string, siteURL string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendOverUserLimitWarningEmail provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendOverUserLimitWarningEmail(_a0 string, locale string, siteURL string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendOverUserLimitWorkspaceSuspendedWarningEmail provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendOverUserLimitWorkspaceSuspendedWarningEmail(_a0 string, locale string, siteURL string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendOverUserSevenDayWarningEmail provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendOverUserSevenDayWarningEmail(_a0 string, locale string, siteURL string) (bool, error) {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
|
||||
r0 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
|
||||
r1 = rf(_a0, locale, siteURL)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendPasswordChangeEmail provides a mock function with given fields: _a0, method, locale, siteURL
|
||||
func (_m *ServiceInterface) SendPasswordChangeEmail(_a0 string, method string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, method, locale, siteURL)
|
||||
@@ -552,48 +405,6 @@ func (_m *ServiceInterface) SendSignInChangeEmail(_a0 string, method string, loc
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendSuspensionEmailToSupport provides a mock function with given fields: _a0, installationID, customerID, subscriptionID, siteURL, userCount
|
||||
func (_m *ServiceInterface) SendSuspensionEmailToSupport(_a0 string, installationID string, customerID string, subscriptionID string, siteURL string, userCount int64) (bool, error) {
|
||||
ret := _m.Called(_a0, installationID, customerID, subscriptionID, siteURL, userCount)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string, string, int64) bool); ok {
|
||||
r0 = rf(_a0, installationID, customerID, subscriptionID, siteURL, userCount)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, string, string, int64) error); ok {
|
||||
r1 = rf(_a0, installationID, customerID, subscriptionID, siteURL, userCount)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendUpgradeEmail provides a mock function with given fields: user, _a1, locale, siteURL, action
|
||||
func (_m *ServiceInterface) SendUpgradeEmail(user string, _a1 string, locale string, siteURL string, action string) (bool, error) {
|
||||
ret := _m.Called(user, _a1, locale, siteURL, action)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string, string) bool); ok {
|
||||
r0 = rf(user, _a1, locale, siteURL, action)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, string, string) error); ok {
|
||||
r1 = rf(user, _a1, locale, siteURL, action)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SendUserAccessTokenAddedEmail provides a mock function with given fields: _a0, locale, siteURL
|
||||
func (_m *ServiceInterface) SendUserAccessTokenAddedEmail(_a0 string, locale string, siteURL string) error {
|
||||
ret := _m.Called(_a0, locale, siteURL)
|
||||
|
||||
@@ -141,16 +141,7 @@ type ServiceInterface interface {
|
||||
SendDeactivateAccountEmail(email string, locale, siteURL string) error
|
||||
SendNotificationMail(to, subject, htmlBody string) error
|
||||
SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader) error
|
||||
SendAtUserLimitWarningEmail(email string, locale string, siteURL string) (bool, error)
|
||||
SendLicenseUpForRenewalEmail(email, name, locale, siteURL, renewalLink string, daysToExpiration int) error
|
||||
SendUpgradeEmail(user, email, locale, siteURL, action string) (bool, error)
|
||||
SendOverUserLimitWarningEmail(email string, locale string, siteURL string) (bool, error)
|
||||
SendOverUserLimitThirtyDayWarningEmail(email string, locale string, siteURL string) (bool, error)
|
||||
SendOverUserLimitNinetyDayWarningEmail(email string, locale string, siteURL string, overLimitDate string) (bool, error)
|
||||
SendOverUserLimitWorkspaceSuspendedWarningEmail(email string, locale string, siteURL string) (bool, error)
|
||||
SendOverUserFourteenDayWarningEmail(email string, locale string, siteURL string, overLimitDate string) (bool, error)
|
||||
SendOverUserSevenDayWarningEmail(email string, locale string, siteURL string) (bool, error)
|
||||
SendSuspensionEmailToSupport(email string, installationID string, customerID string, subscriptionID string, siteURL string, userCount int64) (bool, error)
|
||||
SendPaymentFailedEmail(email string, locale string, failedPayment *model.FailedPayment, siteURL string) (bool, error)
|
||||
SendNoCardPaymentFailedEmail(email string, locale string, siteURL string) error
|
||||
SendRemoveExpiredLicenseEmail(renewalLink, email string, locale, siteURL string) error
|
||||
|
||||
@@ -39,75 +39,3 @@ func TestSendInviteEmailRateLimits(t *testing.T) {
|
||||
assert.Equal(t, "app.email.rate_limit_exceeded.app_error", err.Id)
|
||||
assert.Equal(t, http.StatusRequestEntityTooLarge, err.StatusCode)
|
||||
}
|
||||
|
||||
func TestSendAdminUpgradeRequestEmail(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
mockSubscription := &model.Subscription{
|
||||
ID: "MySubscriptionID",
|
||||
CustomerID: "MyCustomer",
|
||||
ProductID: "SomeProductId",
|
||||
AddOns: []string{},
|
||||
StartAt: 1000000000,
|
||||
EndAt: 2000000000,
|
||||
CreateAt: 1000000000,
|
||||
Seats: 100,
|
||||
DNS: "some.dns.server",
|
||||
IsPaidTier: "false",
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ExperimentalSettings.CloudUserLimit = 10
|
||||
})
|
||||
|
||||
err := th.App.SendAdminUpgradeRequestEmail(th.BasicUser.Username, mockSubscription, model.InviteLimitation)
|
||||
require.Nil(t, err)
|
||||
|
||||
// other attempts by the same user or other users to send emails are blocked by rate limiter
|
||||
err = th.App.SendAdminUpgradeRequestEmail(th.BasicUser.Username, mockSubscription, model.InviteLimitation)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, err.Id, "app.email.rate_limit_exceeded.app_error")
|
||||
|
||||
err = th.App.SendAdminUpgradeRequestEmail(th.BasicUser2.Username, mockSubscription, model.InviteLimitation)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, err.Id, "app.email.rate_limit_exceeded.app_error")
|
||||
}
|
||||
|
||||
func TestSendAdminUpgradeRequestEmailOnJoin(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
|
||||
mockSubscription := &model.Subscription{
|
||||
ID: "MySubscriptionID",
|
||||
CustomerID: "MyCustomer",
|
||||
ProductID: "SomeProductId",
|
||||
AddOns: []string{},
|
||||
StartAt: 1000000000,
|
||||
EndAt: 2000000000,
|
||||
CreateAt: 1000000000,
|
||||
Seats: 100,
|
||||
DNS: "some.dns.server",
|
||||
IsPaidTier: "false",
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ExperimentalSettings.CloudUserLimit = 10
|
||||
})
|
||||
|
||||
err := th.App.SendAdminUpgradeRequestEmail(th.BasicUser.Username, mockSubscription, model.JoinLimitation)
|
||||
require.Nil(t, err)
|
||||
|
||||
// other attempts by the same user or other users to send emails are blocked by rate limiter
|
||||
err = th.App.SendAdminUpgradeRequestEmail(th.BasicUser.Username, mockSubscription, model.JoinLimitation)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, err.Id, "app.email.rate_limit_exceeded.app_error")
|
||||
|
||||
err = th.App.SendAdminUpgradeRequestEmail(th.BasicUser2.Username, mockSubscription, model.JoinLimitation)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, err.Id, "app.email.rate_limit_exceeded.app_error")
|
||||
}
|
||||
|
||||
@@ -69,12 +69,6 @@ func RegisterJobsLdapSyncInterface(f func(*App) ejobs.LdapSyncInterface) {
|
||||
jobsLdapSyncInterface = f
|
||||
}
|
||||
|
||||
var jobsCloudInterface func(*Server) ejobs.CloudJobInterface
|
||||
|
||||
func RegisterJobsCloudInterface(f func(*Server) ejobs.CloudJobInterface) {
|
||||
jobsCloudInterface = f
|
||||
}
|
||||
|
||||
var ldapInterface func(*App) einterfaces.LdapInterface
|
||||
|
||||
func RegisterLdapInterface(f func(*App) einterfaces.LdapInterface) {
|
||||
|
||||
@@ -1144,28 +1144,6 @@ func (a *OpenTracingAppLayer) Channels() *app.Channels {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckAndSendUserLimitWarningEmails(c *request.Context) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckAndSendUserLimitWarningEmails")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.CheckAndSendUserLimitWarningEmails(c)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckCanInviteToSharedChannel(channelId string) error {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckCanInviteToSharedChannel")
|
||||
@@ -1188,28 +1166,6 @@ func (a *OpenTracingAppLayer) CheckCanInviteToSharedChannel(channelId string) er
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckCloudAccountAtLimit() (bool, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckCloudAccountAtLimit")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.CheckCloudAccountAtLimit()
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckForClientSideCert(r *http.Request) (string, string, string) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckForClientSideCert")
|
||||
@@ -5816,28 +5772,6 @@ func (a *OpenTracingAppLayer) GetEnvironmentConfig(filter func(reflect.StructFie
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetErrorListForEmailsOverLimit(emailList []string, cloudUserLimit int64) ([]string, []*model.EmailInviteWithError, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetErrorListForEmailsOverLimit")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1, resultVar2 := a.app.GetErrorListForEmailsOverLimit(emailList, cloudUserLimit)
|
||||
|
||||
if resultVar2 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar2))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1, resultVar2
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetFile(fileID string) ([]byte, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetFile")
|
||||
@@ -9007,28 +8941,6 @@ func (a *OpenTracingAppLayer) GetStatusesByIds(userIDs []string) (map[string]int
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetSubscriptionStats() (*model.SubscriptionStats, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSubscriptionStats")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetSubscriptionStats()
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetSuggestions(c *request.Context, commandArgs *model.CommandArgs, commands []*model.Command, roleID string) []model.AutocompleteSuggestion {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSuggestions")
|
||||
@@ -14461,28 +14373,6 @@ func (a *OpenTracingAppLayer) SendAckToPushProxy(ack *model.PushNotificationAck)
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendAdminUpgradeRequestEmail(username string, subscription *model.Subscription, action string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendAdminUpgradeRequestEmail")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.SendAdminUpgradeRequestEmail(username, subscription, action)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SendAutoResponse(c *request.Context, channel *model.Channel, receiver *model.User, post *model.Post) (bool, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendAutoResponse")
|
||||
|
||||
@@ -1915,11 +1915,6 @@ func (s *Server) initJobs() {
|
||||
s.Jobs.RegisterJobType(model.JobTypeLdapSync, builder.MakeWorker(), builder.MakeScheduler())
|
||||
}
|
||||
|
||||
if jobsCloudInterface != nil {
|
||||
builder := jobsCloudInterface(s)
|
||||
s.Jobs.RegisterJobType(model.JobTypeCloud, builder.MakeWorker(), builder.MakeScheduler())
|
||||
}
|
||||
|
||||
s.Jobs.RegisterJobType(
|
||||
model.JobTypeBlevePostIndexing,
|
||||
indexer.MakeWorker(s.Jobs, s.SearchEngine.BleveEngine.(*bleveengine.BleveEngine)),
|
||||
|
||||
41
app/team.go
41
app/team.go
@@ -1248,47 +1248,6 @@ func (a *App) prepareInviteNewUsersToTeam(teamID, senderId string) (*model.User,
|
||||
return user, team, nil
|
||||
}
|
||||
|
||||
func genEmailInviteWithErrorList(emailList []string) []*model.EmailInviteWithError {
|
||||
invitesNotSent := make([]*model.EmailInviteWithError, len(emailList))
|
||||
for i := range emailList {
|
||||
invite := &model.EmailInviteWithError{
|
||||
Email: emailList[i],
|
||||
Error: model.NewAppError("inviteUsersToTeam", "api.team.invite_members.limit_reached.app_error", map[string]interface{}{"Addresses": emailList[i]}, "", http.StatusBadRequest),
|
||||
}
|
||||
invitesNotSent[i] = invite
|
||||
}
|
||||
return invitesNotSent
|
||||
}
|
||||
|
||||
func (a *App) GetErrorListForEmailsOverLimit(emailList []string, cloudUserLimit int64) ([]string, []*model.EmailInviteWithError, *model.AppError) {
|
||||
var invitesNotSent []*model.EmailInviteWithError
|
||||
if cloudUserLimit <= 0 {
|
||||
return emailList, invitesNotSent, nil
|
||||
}
|
||||
systemUserCount, _ := a.Srv().Store.User().Count(model.UserCountOptions{})
|
||||
remainingUsers := cloudUserLimit - systemUserCount
|
||||
if remainingUsers <= 0 {
|
||||
// No remaining users so all fail
|
||||
invitesNotSent = genEmailInviteWithErrorList(emailList)
|
||||
emailList = nil
|
||||
} else if remainingUsers < int64(len(emailList)) {
|
||||
// Trim the email list to only invite as many users as are remaining in subscription
|
||||
// Set graceful errors for the remaining email addresses
|
||||
emailsAboveLimit := emailList[remainingUsers:]
|
||||
invitesNotSent = genEmailInviteWithErrorList(emailsAboveLimit)
|
||||
// If 1 user remaining we have to prevent 0:0 reslicing
|
||||
if remainingUsers == 1 {
|
||||
email := emailList[0]
|
||||
emailList = nil
|
||||
emailList = append(emailList, email)
|
||||
} else {
|
||||
emailList = emailList[:(remainingUsers - 1)]
|
||||
}
|
||||
}
|
||||
|
||||
return emailList, invitesNotSent, nil
|
||||
}
|
||||
|
||||
func (a *App) InviteNewUsersToTeamGracefully(emailList []string, teamID, senderId string, reminderInterval string) ([]*model.EmailInviteWithError, *model.AppError) {
|
||||
if !*a.Config().ServiceSettings.EnableEmailInvitations {
|
||||
return nil, model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
|
||||
@@ -55,7 +55,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li
|
||||
props["ExperimentalEnablePostMetadata"] = "true"
|
||||
props["ExperimentalEnableClickToReply"] = strconv.FormatBool(*c.ExperimentalSettings.EnableClickToReply)
|
||||
|
||||
props["ExperimentalCloudUserLimit"] = strconv.FormatInt(*c.ExperimentalSettings.CloudUserLimit, 10)
|
||||
props["ExperimentalCloudBilling"] = strconv.FormatBool(*c.ExperimentalSettings.CloudBilling)
|
||||
|
||||
props["ExperimentalEnableAutomaticReplies"] = strconv.FormatBool(*c.TeamSettings.ExperimentalEnableAutomaticReplies)
|
||||
@@ -93,8 +92,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li
|
||||
|
||||
props["EnableEmailInvitations"] = strconv.FormatBool(*c.ServiceSettings.EnableEmailInvitations)
|
||||
|
||||
props["CloudUserLimit"] = strconv.FormatInt(*c.ExperimentalSettings.CloudUserLimit, 10)
|
||||
|
||||
// Set default values for all options that require a license.
|
||||
props["ExperimentalEnableAuthenticationTransfer"] = "true"
|
||||
props["LdapNicknameAttributeSet"] = "false"
|
||||
|
||||
184
i18n/en.json
184
i18n/en.json
@@ -467,14 +467,6 @@
|
||||
"id": "api.cloud.cws_webhook_event_missing_error",
|
||||
"translation": "Webhook event was not handled. Either it is missing or it is not valid."
|
||||
},
|
||||
{
|
||||
"id": "api.cloud.get_admins_emails.error",
|
||||
"translation": "Error getting system admins email."
|
||||
},
|
||||
{
|
||||
"id": "api.cloud.get_subscription.error",
|
||||
"translation": "Error getting cloud subscription."
|
||||
},
|
||||
{
|
||||
"id": "api.cloud.license_error",
|
||||
"translation": "Your license does not support cloud requests."
|
||||
@@ -2867,10 +2859,6 @@
|
||||
"id": "api.team.add_user_to_team_from_invite.guest.app_error",
|
||||
"translation": "Guests are restricted from joining a team via an invite link. Please request a guest email invitation to the team."
|
||||
},
|
||||
{
|
||||
"id": "api.team.cloud.subscription.error",
|
||||
"translation": "Error getting cloud subscription"
|
||||
},
|
||||
{
|
||||
"id": "api.team.demote_user_to_guest.disabled.error",
|
||||
"translation": "Guest accounts are disabled."
|
||||
@@ -2955,10 +2943,6 @@
|
||||
"id": "api.team.invite_members.invalid_email.app_error",
|
||||
"translation": "The following email addresses do not belong to an accepted domain: {{.Addresses}}. Please contact your System Administrator for details."
|
||||
},
|
||||
{
|
||||
"id": "api.team.invite_members.limit_reached.app_error",
|
||||
"translation": "You've reached the free tier user limit"
|
||||
},
|
||||
{
|
||||
"id": "api.team.invite_members.no_one.app_error",
|
||||
"translation": "No one to invite."
|
||||
@@ -3091,26 +3075,6 @@
|
||||
"id": "api.team.update_team_scheme.scheme_scope.error",
|
||||
"translation": "Unable to set the scheme to the team because the supplied scheme is not a team scheme."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.at_limit_info1",
|
||||
"translation": "It looks like you have 10 or more users in your workspace now — that’s great! If you want to invite more team members, consider upgrading Mattermost Cloud now."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.at_limit_info2",
|
||||
"translation": "Alternatively, you can disable users in the Admin Console to open up spots for more users or stay below the free user limit."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.at_limit_info5",
|
||||
"translation": "Alternatively, you can disable users in the System Console to open up spots for users and stay below the free user limit."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.at_limit_subject",
|
||||
"translation": "Mattermost Cloud User Limit Reached"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.at_limit_title",
|
||||
"translation": "You’ve reached the user limit for the free tier "
|
||||
},
|
||||
{
|
||||
"id": "api.templates.cloud_trial_ended_email.start_subscription",
|
||||
"translation": "Start subscription"
|
||||
@@ -3371,122 +3335,10 @@
|
||||
"id": "api.templates.mfa_deactivated_body.title",
|
||||
"translation": "Multi-factor authentication was removed"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_14_days_info1",
|
||||
"translation": "Just a reminder that we have not received payment for your Mattermost subscription invoiced on {{ .OverLimitDate }}. We will soon begin a process to suspend your service if payment is not received."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_14_days_subject",
|
||||
"translation": "Payment is overdue for your Mattermost Cloud subscription"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_14_days_title",
|
||||
"translation": "Payment not received"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_30_days_info1",
|
||||
"translation": "There’s still time to keep your Mattermost Cloud workspace active by resolving issues with your payment method. To avoid suspension update your method of payment."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_30_days_info2",
|
||||
"translation": "If no action is taken, your workspace will be suspended"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_30_days_info2_item1",
|
||||
"translation": "You won't be able to log into your workspace"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_30_days_info2_item2",
|
||||
"translation": "You won’t be able to update payment information without contacting our support team"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_30_days_info2_item3",
|
||||
"translation": "You will lose access to your message history"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_30_days_subject",
|
||||
"translation": " Act to keep your Mattermost Cloud subscription"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_30_days_title",
|
||||
"translation": "Mattermost Cloud Workspace Suspension"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_7_days_info1",
|
||||
"translation": "Mattermost couldn’t process your most recent automatic payment. To keep your service active, please add payment details as soon as possible or your service may be suspended."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_7_days_subject",
|
||||
"translation": "Payment is overdue for your Mattermost Cloud subscription"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_7_days_title",
|
||||
"translation": "No payment method on file"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_90_days_info1",
|
||||
"translation": "This is a final reminder that we have not received payment for your Mattermost Cloud workspace, which has been overdue since {{ .OverLimitDate }}. Tomorrow we will suspend your service."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_90_days_info2",
|
||||
"translation": "To avoid suspension, add your payment information"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_90_days_info3",
|
||||
"translation": "Once your workspace has been suspended, you will not be able to log in to your account or update payment information. You will need to contact our support team to re-activate your service."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_90_days_info4",
|
||||
"translation": "Once your workspace is suspended, all content and data within your workspace will be deleted within 1-3 months."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_90_days_subject",
|
||||
"translation": "Your Mattermost Cloud subscription will soon be suspended"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_90_days_title",
|
||||
"translation": "Your Mattermost Cloud workspace suspension is scheduled for tomorrow"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_fix_now",
|
||||
"translation": "Fix Now"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_info1",
|
||||
"translation": "It looks like you have more than 10 users in your workspace which is beyond the free tier limits of Mattermost Cloud. To avoid any disruption in your Mattermost workspace, please upgrade."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_info2",
|
||||
"translation": "Alternatively, you can disable users in the Admin Console to open up spots for more users or stay below the free user limit."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_subject",
|
||||
"translation": "Mattermost Cloud Workspace Over User Limit"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_suspended_contact_support",
|
||||
"translation": "Contact Support"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_suspended_info1",
|
||||
"translation": "Your Mattermost workspace has been suspended. All contents and data within your workspace will be deleted within 1-3 months."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_suspended_info2",
|
||||
"translation": "Contact us to reactivate your workspace."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_suspended_subject",
|
||||
"translation": "Your Mattermost Cloud subscription has been suspended"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_suspended_title",
|
||||
"translation": "Mattermost Cloud Workspace suspended"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.over_limit_title",
|
||||
"translation": "Your workspace is over the user limit for the free tier"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.password_change_body.info",
|
||||
"translation": "Your password has been updated for {{.TeamDisplayName}} on {{ .TeamURL }} by {{.Method}}."
|
||||
@@ -3631,30 +3483,6 @@
|
||||
"id": "api.templates.signin_change_email.subject",
|
||||
"translation": "[{{ .SiteName }}] Your sign-in method has been updated"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.upgrade_mattermost_cloud",
|
||||
"translation": "Upgrade"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.upgrade_request_info4",
|
||||
"translation": "Because your workspace has reached the user limit for the free version of Mattermost cloud, invitations cannot be sent. Upgrade now to allow more users to join your workspace."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.upgrade_request_info4_2",
|
||||
"translation": "Someone recently tried to join your workspace but was unable to as your workspace has reached the user limit for the free version of Mattermost cloud. Upgrade now to allow more users to join your workspace."
|
||||
},
|
||||
{
|
||||
"id": "api.templates.upgrade_request_subject",
|
||||
"translation": "Mattermost user request upgrade of workspace"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.upgrade_request_title",
|
||||
"translation": "{{ .UserName }} would like to invite members to your workspace"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.upgrade_request_title2",
|
||||
"translation": "New users are unable to join your workspace"
|
||||
},
|
||||
{
|
||||
"id": "api.templates.user_access_token_body.info",
|
||||
"translation": "A personal access token was added to your account on {{ .SiteURL }}. They can be used to access {{.SiteName}} with your account."
|
||||
@@ -4195,14 +4023,6 @@
|
||||
"id": "api.user.update_active.cannot_enable_guest_when_guest_feature_is_disabled.app_error",
|
||||
"translation": "You cannot activate a guest account because Guest Access feature is not enabled."
|
||||
},
|
||||
{
|
||||
"id": "api.user.update_active.cloud_at_limit_check_error",
|
||||
"translation": "Unable to check number of users in your Mattermost cloud instance."
|
||||
},
|
||||
{
|
||||
"id": "api.user.update_active.cloud_at_or_over_limit_check_overcapacity",
|
||||
"translation": "Unable to activate more users as the cloud account is over capacity."
|
||||
},
|
||||
{
|
||||
"id": "api.user.update_active.not_enable.app_error",
|
||||
"translation": "You cannot deactivate yourself because this feature is not enabled. Please contact your System Administrator."
|
||||
@@ -6975,10 +6795,6 @@
|
||||
"id": "ent.api.post.send_notifications_and_forget.push_image_only",
|
||||
"translation": " attached a file."
|
||||
},
|
||||
{
|
||||
"id": "ent.cloud.subscription.error",
|
||||
"translation": "Error getting cloud subscription"
|
||||
},
|
||||
{
|
||||
"id": "ent.cluster.404.app_error",
|
||||
"translation": "Cluster API endpoint not found."
|
||||
|
||||
@@ -7701,18 +7701,6 @@ func (c *Client4) GetSubscription() (*Subscription, *Response, error) {
|
||||
return subscription, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetSubscriptionStats() (*SubscriptionStats, *Response, error) {
|
||||
r, err := c.DoAPIGet(c.cloudRoute()+"/subscription/stats", "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var stats *SubscriptionStats
|
||||
json.NewDecoder(r.Body).Decode(&stats)
|
||||
return stats, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetInvoicesForSubscription() ([]*Invoice, *Response, error) {
|
||||
r, err := c.DoAPIGet(c.cloudRoute()+"/subscription/invoices", "")
|
||||
if err != nil {
|
||||
@@ -7897,26 +7885,6 @@ func (c *Client4) UpdateThreadFollowForUser(userId, teamId, threadId string, sta
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SendAdminUpgradeRequestEmail() (*Response, error) {
|
||||
r, err := c.DoAPIPost(c.cloudRoute()+"/subscription/limitreached/invite", "")
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SendAdminUpgradeRequestEmailOnJoin() (*Response, error) {
|
||||
r, err := c.DoAPIPost(c.cloudRoute()+"/subscription/limitreached/join", "")
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetAllSharedChannels(teamID string, page, perPage int) ([]*SharedChannel, *Response, error) {
|
||||
url := fmt.Sprintf("%s/%s?page=%d&per_page=%d", c.sharedChannelsRoute(), teamID, page, perPage)
|
||||
r, err := c.DoAPIGet(url, "")
|
||||
|
||||
@@ -11,8 +11,6 @@ const (
|
||||
EventTypeSendAdminWelcomeEmail = "send-admin-welcome-email"
|
||||
EventTypeTrialWillEnd = "trial-will-end"
|
||||
EventTypeTrialEnded = "trial-ended"
|
||||
JoinLimitation = "join"
|
||||
InviteLimitation = "invite"
|
||||
)
|
||||
|
||||
var MockCWS string
|
||||
@@ -180,12 +178,6 @@ type FailedPayment struct {
|
||||
type CloudWorkspaceOwner struct {
|
||||
UserName string `json:"username"`
|
||||
}
|
||||
type SubscriptionStats struct {
|
||||
RemainingSeats int `json:"remaining_seats"`
|
||||
IsPaidTier string `json:"is_paid_tier"`
|
||||
IsFreeTrial string `json:"is_free_trial"`
|
||||
}
|
||||
|
||||
type SubscriptionChange struct {
|
||||
ProductID string `json:"product_id"`
|
||||
}
|
||||
|
||||
@@ -904,7 +904,6 @@ type ExperimentalSettings struct {
|
||||
LinkMetadataTimeoutMilliseconds *int64 `access:"experimental_features,write_restrictable,cloud_restrictable"`
|
||||
RestrictSystemAdmin *bool `access:"experimental_features,write_restrictable"`
|
||||
UseNewSAMLLibrary *bool `access:"experimental_features,cloud_restrictable"`
|
||||
CloudUserLimit *int64 `access:"experimental_features,write_restrictable"`
|
||||
CloudBilling *bool `access:"experimental_features,write_restrictable"`
|
||||
EnableSharedChannels *bool `access:"experimental_features"`
|
||||
EnableRemoteClusterService *bool `access:"experimental_features"`
|
||||
@@ -931,11 +930,6 @@ func (s *ExperimentalSettings) SetDefaults() {
|
||||
s.RestrictSystemAdmin = NewBool(false)
|
||||
}
|
||||
|
||||
if s.CloudUserLimit == nil {
|
||||
// User limit 0 is treated as no limit
|
||||
s.CloudUserLimit = NewInt64(0)
|
||||
}
|
||||
|
||||
if s.CloudBilling == nil {
|
||||
s.CloudBilling = NewBool(false)
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@ type FeatureFlags struct {
|
||||
// all other values as false.
|
||||
TestBoolFeature bool
|
||||
|
||||
// Toggle on and off scheduled jobs for cloud user limit emails see MM-29999
|
||||
CloudDelinquentEmailJobsEnabled bool
|
||||
|
||||
// Toggle on and off support for Collapsed Threads
|
||||
CollapsedThreads bool
|
||||
|
||||
@@ -74,7 +71,6 @@ type FeatureFlags struct {
|
||||
func (f *FeatureFlags) SetDefaults() {
|
||||
f.TestFeature = "off"
|
||||
f.TestBoolFeature = false
|
||||
f.CloudDelinquentEmailJobsEnabled = false
|
||||
f.CollapsedThreads = true
|
||||
f.EnableRemoteClusterService = false
|
||||
f.AppsEnabled = false
|
||||
|
||||
@@ -34,9 +34,6 @@ const (
|
||||
SystemFirstAdminSetupComplete = "FirstAdminSetupComplete"
|
||||
AwsMeteringReportInterval = 1
|
||||
AwsMeteringDimensionUsageHrs = "UsageHrs"
|
||||
UserLimitOverageCycleEndDate = "UserLimitOverageCycleEndDate"
|
||||
OverUserLimitForgivenCount = "OverUserLimitForgivenCount"
|
||||
OverUserLimitLastEmailSent = "OverUserLimitLastEmailSent"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -716,7 +716,6 @@ func (ts *TelemetryService) trackConfig() {
|
||||
"restrict_system_admin": *cfg.ExperimentalSettings.RestrictSystemAdmin,
|
||||
"use_new_saml_library": *cfg.ExperimentalSettings.UseNewSAMLLibrary,
|
||||
"cloud_billing": *cfg.ExperimentalSettings.CloudBilling,
|
||||
"cloud_user_limit": *cfg.ExperimentalSettings.CloudUserLimit,
|
||||
"enable_shared_channels": *cfg.ExperimentalSettings.EnableSharedChannels,
|
||||
"enable_remote_cluster_service": *cfg.ExperimentalSettings.EnableRemoteClusterService && cfg.FeatureFlags.EnableRemoteClusterService,
|
||||
})
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
{{define "cloud_upgrade_request_email"}}
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="margin-top: 20px; line-height: 1.7; color: #555;font-family: Arial; font-style: normal; font-weight: bold;">
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd;">
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" style="opacity: 0.5" width="130px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="border-collapse: collapse; max-width: 443px">
|
||||
<tr style="width: 443px">
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
style="padding: 20px 0 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="padding: 0 0 20px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0"
|
||||
width="371px">
|
||||
<tr>
|
||||
<td>
|
||||
<h2 style="margin-bottom: 0; text-align: center; color: #3D3C40; font-weight: bold; margin-top: 10px; line-height: 32px; font-size: 28px; font-family: Arial">{{ .Props.Title }}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="font-weight: normal; color: #3D3C40; font-size: 16px; line-height: 24px; font-family: Arial">
|
||||
{{ .Props.Info4 }}</p>
|
||||
<p style="margin: 31px 0 31px">
|
||||
<a href="{{.Props.SiteURL}}/{{.Props.BillingPath}}" target="_blank"
|
||||
style="font-weight: normal; border-radius: 4px; background: #1C58D9; color: #fff;outline: none; min-width: 200px; padding: 11px 24px 11px 24px; font-size: 14px; font-family: Arial; cursor: pointer; -webkit-appearance: none;text-decoration: none; margin-top: 16px;">{{ .Props.Button }}</a>
|
||||
</p>
|
||||
<p style="font-weight: normal; line-height: 20px; color: #3D3C40; margin-bottom: 24px; font-size: 14px; font-family: Arial">
|
||||
{{ .Props.Info5 }}</p>
|
||||
<img src="{{.Props.SiteURL}}/static/images/credit-card-empty-state.png"
|
||||
width="320px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 443px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="font-family: Arial; padding: 20px 20px 24px 48px; text-align:left;">
|
||||
<h3 style="margin-bottom: 0;">Questions?</h3>
|
||||
<p style="font-weight: normal; margin-top: 0;">{{ .Props.EmailUs }} <a href="mailto:feedback@mattermost.com">feedback@mattermost.com</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="font-family: Arial; line-height: 16px; font-size: 12px; background: #FFF;">
|
||||
<tr>
|
||||
<td
|
||||
style="font-weight: normal; text-align: center;color: #AAA;font-size: 11px;padding-bottom: 10px;padding: 0;line-height: 16px;padding-bottom: 48px;padding-top: 4px;">
|
||||
<p>
|
||||
{{.Props.Organization}}<br>
|
||||
{{.Props.Footer}}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{{end}}
|
||||
@@ -1,110 +0,0 @@
|
||||
{{define "over_user_limit_30_days_body"}}
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="margin-top: 20px; line-height: 1.7; color: #555;font-family: Arial; font-style: normal; font-weight: bold;">
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd;">
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" style="opacity: 0.5"
|
||||
width="130px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="border-collapse: collapse; max-width: 443px">
|
||||
<tr style="width: 443px">
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
style="padding: 20px 0 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="padding: 0 0 64px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0"
|
||||
width="371px">
|
||||
<tr>
|
||||
<td>
|
||||
<h2
|
||||
style="margin-bottom: 0; text-align: center; color: #3D3C40; font-weight: bold; margin-top: 10px; line-height: 32px; font-size: 28px; font-family: Arial">
|
||||
{{ .Props.Title }}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p
|
||||
style="font-weight: normal; color: #3D3C40; font-size: 16px; line-height: 24px; font-family: Arial">
|
||||
{{ .Props.Info1 }}</p>
|
||||
<p style="margin: 31px 0 31px">
|
||||
<a href="{{.Props.SiteURL}}/admin_console/billing/subscription"
|
||||
target="_blank"
|
||||
style="font-weight: normal; border-radius: 4px; background: #1C58D9; color: #fff;outline: none; min-width: 200px; padding: 11px 24px 11px 24px; font-size: 16px; font-family: Arial; cursor: pointer; -webkit-appearance: none;text-decoration: none; margin-top: 16px;">{{ .Props.Button }}</a>
|
||||
</p>
|
||||
<p style="margin-top: 40px; margin-bottom: 24px; font-weight: bold; font-size: 16px; line-height: 24px; color: #3D3C40; font-family: Arial;">{{.Props.Info2}}</p>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="100%">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5; padding-top: 12px; padding-bottom: 12px; text-align:center; color:#3D3C40; font-size: 16px; font-weight: 400; font-family: Arial;" >{{ .Props.Info2Item1 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5; padding-top: 12px; padding-bottom: 12px; text-align:center; color:#3D3C40; font-size: 16px; font-weight: 400; font-family: Arial;">{{ .Props.Info2Item2 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5; padding-top: 12px; padding-bottom: 12px; text-align:center; color:#3D3C40; font-size: 16px; font-weight: 400; font-family: Arial;">{{ .Props.Info2Item3 }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 443px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="font-family: Arial; padding: 20px 20px 24px 48px; text-align:left;">
|
||||
<h3 style="margin-bottom: 0;">Questions?</h3>
|
||||
<p style="font-weight: normal; margin-top: 0;">{{ .Props.EmailUs }} <a
|
||||
href="mailto:feedback@mattermost.com">feedback@mattermost.com</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="font-family: Arial; line-height: 16px; font-size: 12px; background: #FFF;">
|
||||
<tr>
|
||||
<td
|
||||
style="font-weight: normal; text-align: center;color: #AAA;font-size: 11px;padding-bottom: 10px;padding: 0;line-height: 16px;padding-bottom: 48px;padding-top: 4px;">
|
||||
<p>
|
||||
{{.Props.Organization}}<br>
|
||||
{{.Props.Footer}}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{end}}
|
||||
@@ -1,96 +0,0 @@
|
||||
{{define "over_user_limit_7_days_body"}}
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="margin-top: 20px; line-height: 1.7; color: #555;font-family: Arial; font-style: normal; font-weight: bold;">
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd;">
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" style="opacity: 0.5"
|
||||
width="130px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="border-collapse: collapse; max-width: 443px">
|
||||
<tr style="width: 443px">
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
style="padding: 20px 0 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="padding: 0 0 32px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0"
|
||||
width="371px">
|
||||
<tr>
|
||||
<td>
|
||||
<h2
|
||||
style="margin-bottom: 0; text-align: center; color: #3D3C40; font-weight: bold; margin-top: 10px; line-height: 32px; font-size: 28px; font-family: Arial">
|
||||
{{ .Props.Title }}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p
|
||||
style="font-weight: normal; color: #3D3C40; font-size: 16px; line-height: 24px; font-family: Arial">
|
||||
{{ .Props.Info1 }}</p>
|
||||
<p style="margin-top: 16px; margin-bottom: 24px; font-weight: bold; font-size: 16px; line-height: 24px; color: #3D3C40">{{.Props.Info2}}</p>
|
||||
<p style="margin: 31px 0 31px">
|
||||
<a href="{{.Props.SiteURL}}/admin_console/billing/subscription"
|
||||
target="_blank"
|
||||
style="font-weight: normal; border-radius: 4px; background: #1C58D9; color: #fff;outline: none; min-width: 200px; padding: 11px 24px 11px 24px; font-size: 16px; font-family: Arial; cursor: pointer; -webkit-appearance: none;text-decoration: none; margin-top: 16px;">{{ .Props.Button }}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 443px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="font-family: Arial; padding: 20px 20px 24px 48px; text-align:left;">
|
||||
<h3 style="margin-bottom: 0;">Questions?</h3>
|
||||
<p style="font-weight: normal; margin-top: 0;">{{ .Props.EmailUs }} <a
|
||||
href="mailto:feedback@mattermost.com">feedback@mattermost.com</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="font-family: Arial; line-height: 16px; font-size: 12px; background: #FFF;">
|
||||
<tr>
|
||||
<td
|
||||
style="font-weight: normal; text-align: center;color: #AAA;font-size: 11px;padding-bottom: 10px;padding: 0;line-height: 16px;padding-bottom: 48px;padding-top: 4px;">
|
||||
<p>
|
||||
{{.Props.Organization}}<br>
|
||||
{{.Props.Footer}}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{end}}
|
||||
@@ -1,98 +0,0 @@
|
||||
{{define "over_user_limit_90_days_body"}}
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="margin-top: 20px; line-height: 1.7; color: #555;font-family: Arial; font-style: normal; font-weight: bold;">
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd;">
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" style="opacity: 0.5"
|
||||
width="130px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="border-collapse: collapse; max-width: 443px">
|
||||
<tr style="width: 443px">
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
style="padding: 20px 0 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="padding: 0 0 64px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0"
|
||||
width="371px">
|
||||
<tr>
|
||||
<td>
|
||||
<h2
|
||||
style="margin-bottom: 0; text-align: center; color: #3D3C40; font-weight: bold; margin-top: 10px; line-height: 32px; font-size: 28px; font-family: Arial">
|
||||
{{ .Props.Title }}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p
|
||||
style="font-weight: normal; color: #3D3C40; font-size: 16px; line-height: 24px; font-family: Arial">
|
||||
{{ .Props.Info1 }}</p>
|
||||
<p style="margin-top: 16px; margin-bottom: 24px; font-weight: bold; font-size: 16px; line-height: 24px; color: #3D3C40; font-family: Arial">{{.Props.Info2}}</p>
|
||||
<p style="margin: 31px 0 31px">
|
||||
<a href="{{.Props.SiteURL}}/admin_console/billing/subscription"
|
||||
target="_blank"
|
||||
style="font-weight: normal; border-radius: 4px; background: #1C58D9; color: #fff;outline: none; min-width: 200px; padding: 11px 24px 11px 24px; font-size: 16px; font-family: Arial; cursor: pointer; -webkit-appearance: none;text-decoration: none; margin-top: 16px;">{{ .Props.Button }}</a>
|
||||
</p>
|
||||
<p style="margin-top: 24px; margin-bottom: 24px; font-weight: normal; font-size: 16px; line-height: 24px; color: #3D3C40; font-family: Arial">{{.Props.Info3}}</p>
|
||||
<p style="margin-top: 16px; margin-bottom: 24px; font-weight: normal; font-size: 16px; line-height: 24px; color: #3D3C40; font-family: Arial">{{.Props.Info4}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 443px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="font-family: Arial; padding: 20px 20px 24px 48px; text-align:left;">
|
||||
<h3 style="margin-bottom: 0;">Questions?</h3>
|
||||
<p style="font-weight: normal; margin-top: 0;">{{ .Props.EmailUs }} <a
|
||||
href="mailto:feedback@mattermost.com">feedback@mattermost.com</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="font-family: Arial; line-height: 16px; font-size: 12px; background: #FFF;">
|
||||
<tr>
|
||||
<td
|
||||
style="font-weight: normal; text-align: center;color: #AAA;font-size: 11px;padding-bottom: 10px;padding: 0;line-height: 16px;padding-bottom: 48px;padding-top: 4px;">
|
||||
<p>
|
||||
{{.Props.Organization}}<br>
|
||||
{{.Props.Footer}}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{end}}
|
||||
@@ -1,10 +0,0 @@
|
||||
{{define "over_user_limit_support_body"}}
|
||||
<html>
|
||||
<body>
|
||||
<p>Installation with ID: {{ .Props.InstallationID }} has been in arrears for over 30 days, and is scheduled for suspension on {{ .Props.SuspensionDate }}</p><br>
|
||||
<p>Site URL: {{ .Props.SiteURL }}</p><br>
|
||||
<p>Subscription ID: {{ .Props.SubscriptionID }}</p><br>
|
||||
<p>Number of registered users: {{ .Props.UserCount }}</p>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -1,96 +0,0 @@
|
||||
{{define "over_user_limit_workspace_suspended_body"}}
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="margin-top: 20px; line-height: 1.7; color: #555;font-family: Arial; font-style: normal; font-weight: bold;">
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd;">
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" style="opacity: 0.5"
|
||||
width="130px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="border-collapse: collapse; max-width: 443px">
|
||||
<tr style="width: 443px">
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
style="padding: 20px 0 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="padding: 0 0 32px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0"
|
||||
width="371px">
|
||||
<tr>
|
||||
<td>
|
||||
<h2
|
||||
style="margin-bottom: 0; text-align: center; color: #3D3C40; font-weight: bold; margin-top: 10px; line-height: 32px; font-size: 28px; font-family: Arial">
|
||||
{{ .Props.Title }}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p
|
||||
style="font-weight: normal; color: #3D3C40; font-size: 16px; line-height: 24px; font-family: Arial">
|
||||
{{ .Props.Info1 }}</p>
|
||||
<p style="margin-top: 16px; font-weight: bold; font-size: 16px; line-height: 24px; color: #3D3C40">{{.Props.Info2}}</p>
|
||||
<p style="margin: 31px 0 31px">
|
||||
<a href="{{.Props.SiteURL}}/admin_console/billing/subscription"
|
||||
target="_blank"
|
||||
style="font-weight: normal; border-radius: 4px; background: #1C58D9; color: #fff;outline: none; min-width: 200px; padding: 11px 24px 11px 24px; font-size: 16px; font-family: Arial; cursor: pointer; -webkit-appearance: none;text-decoration: none; margin-top: 16px;">{{ .Props.Button }}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 443px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="font-family: Arial; padding: 20px 20px 24px 48px; text-align:left;">
|
||||
<h3 style="margin-bottom: 0;">Questions?</h3>
|
||||
<p style="font-weight: normal; margin-top: 0;">{{ .Props.EmailUs }} <a
|
||||
href="mailto:feedback@mattermost.com">feedback@mattermost.com</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="font-family: Arial; line-height: 16px; font-size: 12px; background: #FFF;">
|
||||
<tr>
|
||||
<td
|
||||
style="font-weight: normal; text-align: center;color: #AAA;font-size: 11px;padding-bottom: 10px;padding: 0;line-height: 16px;padding-bottom: 48px;padding-top: 4px;">
|
||||
<p>
|
||||
{{.Props.Organization}}<br>
|
||||
{{.Props.Footer}}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{end}}
|
||||
@@ -1,91 +0,0 @@
|
||||
{{define "reached_user_limit_body"}}
|
||||
<html>
|
||||
<body>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="margin-top: 20px; line-height: 1.7; color: #555;font-family: Arial; font-style: normal; font-weight: bold;">
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd;">
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" style="opacity: 0.5" width="130px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="border-collapse: collapse; max-width: 443px">
|
||||
<tr style="width: 443px">
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
style="padding: 20px 0 0; text-align: center; margin: 0 auto">
|
||||
<tr>
|
||||
<td style="padding: 0 0 20px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0"
|
||||
width="371px">
|
||||
<tr>
|
||||
<td>
|
||||
<h2 style="margin-bottom: 0; text-align: center; color: #3D3C40; font-weight: bold; margin-top: 10px; line-height: 32px; font-size: 28px; font-family: Arial">{{ .Props.Title }}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="font-weight: normal; color: #3D3C40; font-size: 16px; line-height: 24px; font-family: Arial">
|
||||
{{ .Props.Info1 }}</p>
|
||||
<p style="margin: 31px 0 31px">
|
||||
<a href="{{.Props.SiteURL}}/admin_console/billing/subscription" target="_blank"
|
||||
style="font-weight: normal; border-radius: 4px; background: #1C58D9; color: #fff;outline: none; min-width: 200px; padding: 11px 24px 11px 24px; font-size: 14px; font-family: Arial; cursor: pointer; -webkit-appearance: none;text-decoration: none; margin-top: 16px;">{{ .Props.Button }}</a>
|
||||
</p>
|
||||
<p style="font-weight: normal; line-height: 20px; color: #3D3C40; margin-bottom: 24px; font-size: 14px; font-family: Arial">
|
||||
{{ .Props.Info2 }}</p>
|
||||
<img src="{{.Props.SiteURL}}/static/images/credit-card-empty-state.png"
|
||||
width="320px" alt="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="max-width: 443px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="font-family: Arial; padding: 20px 20px 24px 48px; text-align:left;">
|
||||
<h3 style="margin-bottom: 0;">Questions?</h3>
|
||||
<p style="font-weight: normal; margin-top: 0;">{{ .Props.EmailUs }} <a href="mailto:feedback@mattermost.com">feedback@mattermost.com</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspcing="0" width="612px">
|
||||
<tr>
|
||||
<td style="border-bottom: 1px solid #E5E5E5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="font-family: Arial; line-height: 16px; font-size: 12px; background: #FFF;">
|
||||
<tr>
|
||||
<td
|
||||
style="font-weight: normal; text-align: center;color: #AAA;font-size: 11px;padding-bottom: 10px;padding: 0;line-height: 16px;padding-bottom: 48px;padding-top: 4px;">
|
||||
<p>
|
||||
{{.Props.Organization}}<br>
|
||||
{{.Props.Footer}}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user