Add metric warning support (announcement bar and DM) (#14483)
* Admin. Advisory: Add warning for number of active users metric status Co-authored-by: Catalin Tomai <catalin.tomai@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
56fb31f06f
Коммит
549e5b57cd
@@ -221,7 +221,7 @@ func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError {
|
||||
|
||||
T := utils.GetUserTranslations(user.Locale)
|
||||
license := a.Srv().License()
|
||||
if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance); err != nil {
|
||||
if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance, ""); err != nil {
|
||||
return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
||||
252
app/app.go
252
app/app.go
@@ -5,16 +5,20 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/go-i18n/i18n"
|
||||
goi18n "github.com/mattermost/go-i18n/i18n"
|
||||
"github.com/mattermost/mattermost-server/v5/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/httpservice"
|
||||
"github.com/mattermost/mattermost-server/v5/services/imageproxy"
|
||||
"github.com/mattermost/mattermost-server/v5/services/mailservice"
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine"
|
||||
"github.com/mattermost/mattermost-server/v5/services/timezones"
|
||||
"github.com/mattermost/mattermost-server/v5/utils"
|
||||
@@ -107,6 +111,7 @@ func (a *App) InitServer() {
|
||||
if a.Srv().runjobs {
|
||||
a.Srv().Go(func() {
|
||||
runLicenseExpirationCheckJob(a)
|
||||
runCheckNumberOfActiveUsersWarnMetricStatusJob(a)
|
||||
})
|
||||
}
|
||||
a.srv.RunJobs()
|
||||
@@ -184,6 +189,253 @@ func (s *Server) getFirstServerRunTimestamp() (int64, *model.AppError) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (a *App) GetWarnMetricsStatus() (map[string]*model.WarnMetricStatus, *model.AppError) {
|
||||
systemDataList, appErr := a.Srv().Store.System().Get()
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
result := map[string]*model.WarnMetricStatus{}
|
||||
for key, value := range systemDataList {
|
||||
if strings.HasPrefix(key, model.WARN_METRIC_STATUS_STORE_PREFIX) {
|
||||
if warnMetric, ok := model.WarnMetricsTable[key]; ok {
|
||||
if !warnMetric.IsBotOnly && value == model.WARN_METRIC_STATUS_LIMIT_REACHED {
|
||||
result[key], _ = a.getWarnMetricStatusAndDisplayTextsForId(key, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *App) getWarnMetricStatusAndDisplayTextsForId(warnMetricId string, T i18n.TranslateFunc) (*model.WarnMetricStatus, *model.WarnMetricDisplayTexts) {
|
||||
var warnMetricStatus *model.WarnMetricStatus
|
||||
var warnMetricDisplayTexts = &model.WarnMetricDisplayTexts{}
|
||||
|
||||
if warnMetric, ok := model.WarnMetricsTable[warnMetricId]; ok {
|
||||
warnMetricStatus = &model.WarnMetricStatus{
|
||||
Id: warnMetric.Id,
|
||||
Limit: warnMetric.Limit,
|
||||
Acked: false,
|
||||
}
|
||||
|
||||
if T == nil {
|
||||
mlog.Debug("No translation function")
|
||||
return warnMetricStatus, nil
|
||||
}
|
||||
|
||||
warnMetricDisplayTexts.BotMailToBody = T("api.server.warn_metric.bot_response.number_of_users.mailto_body", map[string]interface{}{"Limit": warnMetric.Limit})
|
||||
warnMetricDisplayTexts.EmailBody = T("api.templates.warn_metric_ack.number_of_active_users.body", map[string]interface{}{"Limit": warnMetric.Limit})
|
||||
|
||||
switch warnMetricId {
|
||||
case model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200:
|
||||
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_active_users_200.notification_title")
|
||||
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_200.notification_body")
|
||||
case model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_400:
|
||||
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_active_users_400.notification_title")
|
||||
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_400.notification_body")
|
||||
case model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500:
|
||||
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_active_users_500.notification_title")
|
||||
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_500.notification_body")
|
||||
default:
|
||||
mlog.Error("Invalid metric id", mlog.String("id", warnMetricId))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return warnMetricStatus, warnMetricDisplayTexts
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *App) notifyAdminsOfWarnMetricStatus(warnMetricId string) *model.AppError {
|
||||
perPage := 25
|
||||
userOptions := &model.UserGetOptions{
|
||||
Page: 0,
|
||||
PerPage: perPage,
|
||||
Role: model.SYSTEM_ADMIN_ROLE_ID,
|
||||
Inactive: false,
|
||||
}
|
||||
|
||||
// get sysadmins
|
||||
var sysAdmins []*model.User
|
||||
for {
|
||||
sysAdminsList, err := a.GetUsers(userOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(sysAdminsList) == 0 {
|
||||
return model.NewAppError("NotifyAdminsOfWarnMetricStatus", "app.system.warn_metric.notification.empty_admin_list.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
sysAdmins = append(sysAdmins, sysAdminsList...)
|
||||
|
||||
if len(sysAdminsList) < perPage {
|
||||
mlog.Debug("Number of system admins is less than page limit", mlog.Int("count", len(sysAdminsList)))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
T := utils.GetUserTranslations(sysAdmins[0].Locale)
|
||||
warnMetricsBot := &model.Bot{
|
||||
Username: model.BOT_WARN_METRIC_BOT_USERNAME,
|
||||
DisplayName: T("app.system.warn_metric.bot_displayname"),
|
||||
Description: "",
|
||||
OwnerId: sysAdmins[0].Id,
|
||||
}
|
||||
|
||||
bot, err := a.getOrCreateWarnMetricsBot(warnMetricsBot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, sysAdmin := range sysAdmins {
|
||||
T := utils.GetUserTranslations(sysAdmin.Locale)
|
||||
bot.DisplayName = T("app.system.warn_metric.bot_displayname")
|
||||
bot.Description = T("app.system.warn_metric.bot_description")
|
||||
|
||||
channel, appErr := a.GetOrCreateDirectChannel(bot.UserId, sysAdmin.Id)
|
||||
if appErr != nil {
|
||||
mlog.Error("Cannot create channel for system bot notification!", mlog.String("Admin Id", sysAdmin.Id))
|
||||
return appErr
|
||||
}
|
||||
|
||||
warnMetricStatus, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(warnMetricId, T)
|
||||
if warnMetricStatus == nil {
|
||||
return model.NewAppError("NotifyAdminsOfWarnMetricStatus", "app.system.warn_metric.notification.invalid_metric.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
botPost := &model.Post{
|
||||
UserId: bot.UserId,
|
||||
ChannelId: channel.Id,
|
||||
Type: model.POST_SYSTEM_WARN_METRIC_STATUS,
|
||||
Message: "",
|
||||
}
|
||||
|
||||
actions := []*model.PostAction{}
|
||||
actions = append(actions,
|
||||
&model.PostAction{
|
||||
Id: "contactUs",
|
||||
Name: T("api.server.warn_metric.contact_us"),
|
||||
Type: model.POST_ACTION_TYPE_BUTTON,
|
||||
Options: []*model.PostActionOptions{
|
||||
{
|
||||
Text: "TrackEventId",
|
||||
Value: warnMetricId,
|
||||
},
|
||||
{
|
||||
Text: "ActionExecutingMessage",
|
||||
Value: T("api.server.warn_metric.contacting_us"),
|
||||
},
|
||||
},
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"bot_user_id": bot.UserId,
|
||||
"force_ack": false,
|
||||
},
|
||||
URL: fmt.Sprintf("/warn_metrics/ack/%s", warnMetricId),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
attachments := []*model.SlackAttachment{{
|
||||
AuthorName: "",
|
||||
Title: warnMetricDisplayTexts.BotTitle,
|
||||
Text: warnMetricDisplayTexts.BotMessageBody,
|
||||
Actions: actions,
|
||||
}}
|
||||
model.ParseSlackAttachment(botPost, attachments)
|
||||
|
||||
mlog.Debug("Send admin advisory for metric", mlog.String("warnMetricId", warnMetricId), mlog.String("userid", botPost.UserId))
|
||||
if _, err := a.CreatePostAsUser(botPost, a.Session().Id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError {
|
||||
if warnMetric, ok := model.WarnMetricsTable[warnMetricId]; ok {
|
||||
data, err := a.Srv().Store.System().GetByName(warnMetric.Id)
|
||||
if err == nil && data != nil && data.Value == model.WARN_METRIC_STATUS_ACK {
|
||||
mlog.Debug("This metric warning has already been acknowledged")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !forceAck {
|
||||
if len(*a.Config().EmailSettings.SMTPServer) == 0 {
|
||||
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.missing_server.app_error", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusInternalServerError)
|
||||
}
|
||||
T := utils.GetUserTranslations(sender.Locale)
|
||||
bodyPage := a.Srv().EmailService.newEmailTemplate("warn_metric_ack", sender.Locale)
|
||||
bodyPage.Props["ContactNameHeader"] = T("api.templates.warn_metric_ack.body.contact_name_header")
|
||||
bodyPage.Props["ContactNameValue"] = sender.GetFullName()
|
||||
bodyPage.Props["ContactEmailHeader"] = T("api.templates.warn_metric_ack.body.contact_email_header")
|
||||
bodyPage.Props["ContactEmailValue"] = sender.Email
|
||||
|
||||
//same definition as the active users count metric displayed in the SystemConsole Analytics section
|
||||
registeredUsersCount, cerr := a.Srv().Store.User().Count(model.UserCountOptions{})
|
||||
if cerr != nil {
|
||||
mlog.Error("Error retrieving the number of registered users", mlog.Err(cerr))
|
||||
} else {
|
||||
bodyPage.Props["RegisteredUsersHeader"] = T("api.templates.warn_metric_ack.body.registered_users_header")
|
||||
bodyPage.Props["RegisteredUsersValue"] = registeredUsersCount
|
||||
}
|
||||
bodyPage.Props["SiteURLHeader"] = T("api.templates.warn_metric_ack.body.site_url_header")
|
||||
bodyPage.Props["SiteURL"] = a.GetSiteURL()
|
||||
bodyPage.Props["DiagnosticIdHeader"] = T("api.templates.warn_metric_ack.body.diagnostic_id_header")
|
||||
bodyPage.Props["DiagnosticIdValue"] = a.DiagnosticId()
|
||||
bodyPage.Props["Footer"] = T("api.templates.warn_metric_ack.footer")
|
||||
|
||||
warnMetricStatus, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(warnMetricId, T)
|
||||
if warnMetricStatus == nil {
|
||||
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.invalid_warn_metric.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
subject := T("api.templates.warn_metric_ack.subject")
|
||||
bodyPage.Props["Title"] = warnMetricDisplayTexts.EmailBody
|
||||
|
||||
if err = mailservice.SendMailUsingConfig(model.MM_SUPPORT_ADDRESS, subject, bodyPage.Render(), a.Config(), false, sender.Email); err != nil {
|
||||
mlog.Error("Error while sending email", mlog.String("destination email", model.MM_SUPPORT_ADDRESS), mlog.Err(err))
|
||||
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Debug("Disable the monitoring of all warn metrics")
|
||||
err = a.setWarnMetricsStatus(model.WARN_METRIC_STATUS_ACK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !warnMetric.IsBotOnly && !isBot {
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_WARN_METRIC_STATUS_REMOVED, "", "", "", nil)
|
||||
message.Add("warnMetricId", warnMetric.Id)
|
||||
a.Publish(message)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) setWarnMetricsStatus(status string) *model.AppError {
|
||||
for _, warnMetric := range model.WarnMetricsTable {
|
||||
a.setWarnMetricsStatusForId(warnMetric.Id, status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) setWarnMetricsStatusForId(warnMetricId string, status string) *model.AppError {
|
||||
mlog.Info("Storing user acknowledgement for warn metric", mlog.String("warnMetricId", warnMetricId))
|
||||
if err := a.Srv().Store.System().SaveOrUpdate(&model.System{
|
||||
Name: warnMetricId,
|
||||
Value: status,
|
||||
}); err != nil {
|
||||
mlog.Error("Unable to write to database.", mlog.Err(err))
|
||||
return model.NewAppError("setWarnMetricsStatusForId", "app.system.warn_metric.store.app_error", map[string]interface{}{"WarnMetricName": warnMetricId}, "", http.StatusInternalServerError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) Srv() *Server {
|
||||
return a.srv
|
||||
}
|
||||
|
||||
@@ -692,6 +692,7 @@ type AppIface interface {
|
||||
GetUsersWithoutTeamPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError)
|
||||
GetVerifyEmailToken(token string) (*model.Token, *model.AppError)
|
||||
GetViewUsersRestrictions(userId string) (*model.ViewUsersRestrictions, *model.AppError)
|
||||
GetWarnMetricsStatus() (map[string]*model.WarnMetricStatus, *model.AppError)
|
||||
HTTPService() httpservice.HTTPService
|
||||
Handle404(w http.ResponseWriter, r *http.Request)
|
||||
HandleCommandResponse(command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError)
|
||||
@@ -753,6 +754,7 @@ type AppIface interface {
|
||||
NewPluginAPI(manifest *model.Manifest) plugin.API
|
||||
Notification() einterfaces.NotificationInterface
|
||||
NotificationsLog() *mlog.Logger
|
||||
NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError
|
||||
OpenInteractiveDialog(request model.OpenDialogRequest) *model.AppError
|
||||
OriginChecker() func(*http.Request) bool
|
||||
PatchChannel(channel *model.Channel, patch *model.ChannelPatch, userId string) (*model.Channel, *model.AppError)
|
||||
|
||||
45
app/bot.go
45
app/bot.go
@@ -65,6 +65,51 @@ func (a *App) CreateBot(bot *model.Bot) (*model.Bot, *model.AppError) {
|
||||
return savedBot, nil
|
||||
}
|
||||
|
||||
func (a *App) getOrCreateWarnMetricsBot(botDef *model.Bot) (*model.Bot, *model.AppError) {
|
||||
botUser, appErr := a.GetUserByUsername(botDef.Username)
|
||||
if appErr != nil {
|
||||
if appErr.StatusCode != http.StatusNotFound {
|
||||
mlog.Error(appErr.Error())
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
// cannot find this bot user, save the user
|
||||
user, err := a.Srv().Store.User().Save(model.UserFromBot(botDef))
|
||||
if err != nil {
|
||||
mlog.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
botDef.UserId = user.Id
|
||||
|
||||
//save the bot
|
||||
savedBot, nErr := a.Srv().Store.Bot().Save(botDef)
|
||||
if nErr != nil {
|
||||
a.Srv().Store.User().PermanentDelete(savedBot.UserId)
|
||||
var nAppErr *model.AppError
|
||||
switch {
|
||||
case errors.As(nErr, &nAppErr): // in case we haven't converted to plain error.
|
||||
return nil, nAppErr
|
||||
default: // last fallback in case it doesn't map to an existing app error.
|
||||
return nil, model.NewAppError("getOrCreateWarnMetricsBot", "app.bot.createbot.internal_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
return savedBot, nil
|
||||
}
|
||||
|
||||
if botUser == nil {
|
||||
return nil, model.NewAppError("getOrCreateWarnMetricsBot", "app.bot.createbot.internal_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
//return the bot for this user
|
||||
savedBot, appErr := a.GetBot(botUser.Id, false)
|
||||
if appErr != nil {
|
||||
mlog.Error(appErr.Error())
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return savedBot, nil
|
||||
}
|
||||
|
||||
// PatchBot applies the given patch to the bot and corresponding user.
|
||||
func (a *App) PatchBot(botUserId string, botPatch *model.BotPatch) (*model.Bot, *model.AppError) {
|
||||
bot, err := a.GetBot(botUserId, true)
|
||||
|
||||
@@ -57,6 +57,7 @@ const (
|
||||
TRACK_ELASTICSEARCH = "elasticsearch"
|
||||
TRACK_GROUPS = "groups"
|
||||
TRACK_CHANNEL_MODERATION = "channel_moderation"
|
||||
TRACK_WARN_METRICS = "warn_metrics"
|
||||
|
||||
TRACK_ACTIVITY = "activity"
|
||||
TRACK_LICENSE = "license"
|
||||
@@ -83,6 +84,7 @@ func (s *Server) sendDailyDiagnostics(override bool) {
|
||||
s.trackElasticsearch()
|
||||
s.trackGroups()
|
||||
s.trackChannelModeration()
|
||||
s.trackWarnMetrics()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1088,3 +1090,19 @@ func (s *Server) trackChannelModeration() {
|
||||
"use_channel_mentions_guest_disabled_count": useChannelMentionsGuest,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) trackWarnMetrics() {
|
||||
systemDataList, appErr := s.Store.System().Get()
|
||||
if appErr != nil {
|
||||
return
|
||||
}
|
||||
for key, value := range systemDataList {
|
||||
if strings.HasPrefix(key, model.WARN_METRIC_STATUS_STORE_PREFIX) {
|
||||
if _, ok := model.WarnMetricsTable[key]; ok {
|
||||
s.SendDiagnostic(TRACK_WARN_METRICS, map[string]interface{}{
|
||||
key: value != "false",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,15 +550,19 @@ func (es *EmailService) sendNotificationMail(to, subject, htmlBody string) *mode
|
||||
}
|
||||
|
||||
func (es *EmailService) sendMail(to, subject, htmlBody string) *model.AppError {
|
||||
return es.sendMailWithCC(to, subject, htmlBody, "")
|
||||
}
|
||||
|
||||
func (es *EmailService) sendMailWithCC(to, subject, htmlBody string, ccMail string) *model.AppError {
|
||||
license := es.srv.License()
|
||||
return mailservice.SendMailUsingConfig(to, subject, htmlBody, es.srv.Config(), license != nil && *license.Features.Compliance)
|
||||
return mailservice.SendMailUsingConfig(to, subject, htmlBody, es.srv.Config(), license != nil && *license.Features.Compliance, ccMail)
|
||||
}
|
||||
|
||||
func (es *EmailService) sendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader) *model.AppError {
|
||||
license := es.srv.License()
|
||||
config := es.srv.Config()
|
||||
|
||||
return mailservice.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, config, license != nil && *license.Features.Compliance)
|
||||
return mailservice.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, config, license != nil && *license.Features.Compliance, "")
|
||||
}
|
||||
|
||||
func (es *EmailService) CreateVerifyEmailToken(userId string, newEmail string) (*model.Token, *model.AppError) {
|
||||
|
||||
@@ -26,10 +26,12 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/mattermost/mattermost-server/v5/utils"
|
||||
@@ -211,11 +213,20 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
||||
return "", appErr
|
||||
}
|
||||
|
||||
resp, appErr := a.DoActionRequest(upstreamURL, upstreamRequest.ToJson())
|
||||
if appErr != nil {
|
||||
return "", appErr
|
||||
var resp *http.Response
|
||||
if strings.HasPrefix(upstreamURL, "/warn_metrics/") {
|
||||
appErr = a.doLocalWarnMetricsRequest(upstreamURL, upstreamRequest)
|
||||
if appErr != nil {
|
||||
return "", appErr
|
||||
}
|
||||
return "", nil
|
||||
} else {
|
||||
resp, appErr = a.DoActionRequest(upstreamURL, upstreamRequest.ToJson())
|
||||
if appErr != nil {
|
||||
return "", appErr
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var response model.PostActionIntegrationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
@@ -398,6 +409,129 @@ func (a *App) doPluginRequest(method, rawURL string, values url.Values, body []b
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a *App) doLocalWarnMetricsRequest(rawURL string, upstreamRequest *model.PostActionIntegrationRequest) *model.AppError {
|
||||
_, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return model.NewAppError("doLocalWarnMetricsRequest", "api.post.do_action.action_integration.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
warnMetricId := filepath.Base(rawURL)
|
||||
if warnMetricId == "" {
|
||||
return model.NewAppError("doLocalWarnMetricsRequest", "api.post.do_action.action_integration.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
user, appErr := a.GetUser(a.Session().UserId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
botPost := &model.Post{
|
||||
UserId: upstreamRequest.Context["bot_user_id"].(string),
|
||||
ChannelId: upstreamRequest.ChannelId,
|
||||
HasReactions: true,
|
||||
}
|
||||
|
||||
forceAck := upstreamRequest.Context["force_ack"].(bool)
|
||||
|
||||
if appErr = a.NotifyAndSetWarnMetricAck(warnMetricId, user, forceAck, true); appErr != nil {
|
||||
if forceAck {
|
||||
return appErr
|
||||
}
|
||||
mailtoLinkText := a.buildWarnMetricMailtoLink(warnMetricId, user)
|
||||
botPost.Message = ":warning: " + utils.T("api.server.warn_metric.bot_response.notification_failure.message")
|
||||
actions := []*model.PostAction{}
|
||||
actions = append(actions,
|
||||
&model.PostAction{
|
||||
Id: "emailUs",
|
||||
Name: utils.T("api.server.warn_metric.email_us"),
|
||||
Type: model.POST_ACTION_TYPE_BUTTON,
|
||||
Options: []*model.PostActionOptions{
|
||||
{
|
||||
Text: "WarnMetricMailtoUrl",
|
||||
Value: mailtoLinkText,
|
||||
},
|
||||
{
|
||||
Text: "TrackEventId",
|
||||
Value: warnMetricId,
|
||||
},
|
||||
},
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"bot_user_id": botPost.UserId,
|
||||
"force_ack": true,
|
||||
},
|
||||
URL: fmt.Sprintf("/warn_metrics/ack/%s", model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500),
|
||||
},
|
||||
},
|
||||
)
|
||||
attachements := []*model.SlackAttachment{{
|
||||
AuthorName: "",
|
||||
Title: "",
|
||||
Actions: actions,
|
||||
Text: utils.T("api.server.warn_metric.bot_response.notification_failure.body"),
|
||||
}}
|
||||
model.ParseSlackAttachment(botPost, attachements)
|
||||
} else {
|
||||
botPost.Message = ":white_check_mark: " + utils.T("api.server.warn_metric.bot_response.notification_success.message")
|
||||
}
|
||||
|
||||
if _, err := a.CreatePostAsUser(botPost, a.Session().Id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MailToLinkContent struct {
|
||||
MetricId string `json:"metric_id"`
|
||||
MailRecipient string `json:"mail_recipient"`
|
||||
MailCC string `json:"mail_cc"`
|
||||
MailSubject string `json:"mail_subject"`
|
||||
MailBody string `json:"mail_body"`
|
||||
}
|
||||
|
||||
func (mlc *MailToLinkContent) ToJson() string {
|
||||
b, _ := json.Marshal(mlc)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (a *App) buildWarnMetricMailtoLink(warnMetricId string, user *model.User) string {
|
||||
T := utils.GetUserTranslations(user.Locale)
|
||||
_, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(warnMetricId, T)
|
||||
|
||||
mailBody := warnMetricDisplayTexts.BotMailToBody
|
||||
mailBody += T("api.server.warn_metric.bot_response.mailto_contact_header", map[string]interface{}{"Contact": user.GetFullName()})
|
||||
mailBody += "\r\n"
|
||||
mailBody += T("api.server.warn_metric.bot_response.mailto_email_header", map[string]interface{}{"Email": user.Email})
|
||||
mailBody += "\r\n"
|
||||
|
||||
registeredUsersCount, err := a.Srv().Store.User().Count(model.UserCountOptions{})
|
||||
if err != nil {
|
||||
mlog.Error("Error retrieving the number of registered users", mlog.Err(err))
|
||||
} else {
|
||||
mailBody += utils.T("api.server.warn_metric.bot_response.mailto_registered_users_header", map[string]interface{}{"NoRegisteredUsers": registeredUsersCount})
|
||||
mailBody += "\r\n"
|
||||
}
|
||||
|
||||
mailBody += T("api.server.warn_metric.bot_response.mailto_site_url_header", map[string]interface{}{"SiteUrl": a.GetSiteURL()})
|
||||
mailBody += "\r\n"
|
||||
|
||||
mailBody += T("api.server.warn_metric.bot_response.mailto_diagnostic_id_header", map[string]interface{}{"DiagnosticId": a.DiagnosticId()})
|
||||
mailBody += "\r\n"
|
||||
|
||||
mailBody += T("api.server.warn_metric.bot_response.mailto_footer")
|
||||
|
||||
mailToLinkContent := &MailToLinkContent{
|
||||
MetricId: warnMetricId,
|
||||
MailRecipient: "support@mattermost.com",
|
||||
MailCC: user.Email,
|
||||
MailSubject: T("api.server.warn_metric.bot_response.mailto_subject"),
|
||||
MailBody: mailBody,
|
||||
}
|
||||
|
||||
return mailToLinkContent.ToJson()
|
||||
}
|
||||
|
||||
func (a *App) DoLocalRequest(rawURL string, body []byte) (*http.Response, *model.AppError) {
|
||||
return a.doPluginRequest("POST", rawURL, nil, body)
|
||||
}
|
||||
|
||||
@@ -8940,6 +8940,28 @@ func (a *OpenTracingAppLayer) GetViewUsersRestrictions(userId string) (*model.Vi
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetWarnMetricsStatus() (map[string]*model.WarnMetricStatus, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetWarnMetricsStatus")
|
||||
|
||||
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.GetWarnMetricsStatus()
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) Handle404(w http.ResponseWriter, r *http.Request) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.Handle404")
|
||||
@@ -10255,6 +10277,28 @@ func (a *OpenTracingAppLayer) NewWebHub() *app.Hub {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NotifyAndSetWarnMetricAck")
|
||||
|
||||
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.NotifyAndSetWarnMetricAck(warnMetricId, sender, forceAck, isBot)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) NotifySessionsExpired() *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NotifySessionsExpired")
|
||||
|
||||
@@ -115,7 +115,7 @@ func (s *Server) DoSecurityUpdateCheck() {
|
||||
for _, user := range users {
|
||||
mlog.Info("Sending security bulletin", mlog.String("bulletin_id", bulletin.Id), mlog.String("user_email", user.Email))
|
||||
license := s.License()
|
||||
mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), s.Config(), license != nil && *license.Features.Compliance)
|
||||
mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), s.Config(), license != nil && *license.Features.Compliance, "")
|
||||
}
|
||||
|
||||
bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id}
|
||||
|
||||
@@ -1090,6 +1090,13 @@ func runLicenseExpirationCheckJob(a *App) {
|
||||
}, time.Hour*24)
|
||||
}
|
||||
|
||||
func runCheckNumberOfActiveUsersWarnMetricStatusJob(a *App) {
|
||||
doCheckNumberOfActiveUsersWarnMetricStatus(a)
|
||||
model.CreateRecurringTask("Check Number Of Active Users Warn Metric Status", func() {
|
||||
doCheckNumberOfActiveUsersWarnMetricStatus(a)
|
||||
}, time.Hour*24)
|
||||
}
|
||||
|
||||
func doSecurity(s *Server) {
|
||||
s.DoSecurityUpdateCheck()
|
||||
}
|
||||
@@ -1117,6 +1124,58 @@ func doSessionCleanup(s *Server) {
|
||||
s.Store.Session().Cleanup(model.GetMillis(), SESSIONS_CLEANUP_BATCH_SIZE)
|
||||
}
|
||||
|
||||
func doCheckNumberOfActiveUsersWarnMetricStatus(a *App) {
|
||||
license := a.Srv().License()
|
||||
if license != nil {
|
||||
mlog.Debug("License is present, skip this check")
|
||||
return
|
||||
}
|
||||
|
||||
numberOfActiveUsers, err := a.Srv().Store.User().Count(model.UserCountOptions{})
|
||||
if err != nil {
|
||||
mlog.Error("Error to get active registered users.", mlog.Err(err))
|
||||
}
|
||||
|
||||
warnMetrics := []model.WarnMetric{}
|
||||
if numberOfActiveUsers < model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200].Limit {
|
||||
return
|
||||
} else if numberOfActiveUsers >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200].Limit && numberOfActiveUsers < model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_400].Limit {
|
||||
warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200])
|
||||
} else if numberOfActiveUsers >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_400].Limit && numberOfActiveUsers < model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500].Limit {
|
||||
warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_400])
|
||||
} else {
|
||||
warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500])
|
||||
}
|
||||
|
||||
for _, warnMetric := range warnMetrics {
|
||||
data, err := a.Srv().Store.System().GetByName(warnMetric.Id)
|
||||
if err == nil && data != nil && (data.Value == model.WARN_METRIC_STATUS_ACK || data.Value == model.WARN_METRIC_STATUS_RUNONCE) {
|
||||
mlog.Debug("This metric warning has already been acked or should only run once")
|
||||
continue
|
||||
}
|
||||
|
||||
if err = a.Srv().Store.System().SaveOrUpdate(&model.System{Name: warnMetric.Id, Value: model.WARN_METRIC_STATUS_LIMIT_REACHED}); err != nil {
|
||||
mlog.Error("Unable to write to database.", mlog.String("id", warnMetric.Id), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
warnMetricStatus, _ := a.getWarnMetricStatusAndDisplayTextsForId(warnMetric.Id, nil)
|
||||
|
||||
if !warnMetric.IsBotOnly {
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_WARN_METRIC_STATUS_RECEIVED, "", "", "", nil)
|
||||
message.Add("warnMetricStatus", warnMetricStatus.ToJson())
|
||||
a.Publish(message)
|
||||
}
|
||||
|
||||
if err = a.notifyAdminsOfWarnMetricStatus(warnMetric.Id); err != nil {
|
||||
mlog.Error("Failed to send notifications to admin users.", mlog.Err(err))
|
||||
}
|
||||
|
||||
if warnMetric.IsRunOnce {
|
||||
a.setWarnMetricsStatusForId(warnMetric.Id, model.WARN_METRIC_STATUS_RUNONCE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doLicenseExpirationCheck(a *App) {
|
||||
a.Srv().LoadLicense()
|
||||
license := a.Srv().License()
|
||||
|
||||
Ссылка в новой задаче
Block a user