mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-09 07:54:28 +08:00
951 lines
32 KiB
Go
951 lines
32 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package models
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"html"
|
|
"html/template"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/util/rbacscope"
|
|
"yunion.io/x/pkg/util/sets"
|
|
"yunion.io/x/pkg/utils"
|
|
"yunion.io/x/sqlchemy"
|
|
|
|
api "yunion.io/x/onecloud/pkg/apis/notify"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
|
"yunion.io/x/onecloud/pkg/httperrors"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
|
modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
|
|
"yunion.io/x/onecloud/pkg/notify/options"
|
|
"yunion.io/x/onecloud/pkg/util/logclient"
|
|
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
|
)
|
|
|
|
type SNotificationManager struct {
|
|
db.SStatusStandaloneResourceBaseManager
|
|
}
|
|
|
|
var NotificationManager *SNotificationManager
|
|
|
|
func init() {
|
|
NotificationManager = &SNotificationManager{
|
|
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
|
|
SNotification{},
|
|
"notifications_tbl",
|
|
"notification",
|
|
"notifications",
|
|
),
|
|
}
|
|
NotificationManager.SetVirtualObject(NotificationManager)
|
|
NotificationManager.TableSpec().AddIndex(false, "deleted", "contact_type", "topic_type")
|
|
}
|
|
|
|
// 站内信
|
|
type SNotification struct {
|
|
db.SStatusStandaloneResourceBase
|
|
|
|
ContactType string `width:"128" nullable:"true" create:"optional" list:"user" get:"user"`
|
|
// swagger:ignore
|
|
Topic string `width:"128" nullable:"true" create:"required" list:"user" get:"user"`
|
|
Priority string `width:"16" nullable:"true" create:"optional" list:"user" get:"user"`
|
|
// swagger:ignore
|
|
Message string `create:"required"`
|
|
// swagger:ignore
|
|
TopicType string `json:"topic_type" width:"20" nullable:"true" update:"user" list:"user"`
|
|
// swagger:ignore
|
|
TopicId string `width:"128" nullable:"true" list:"user" get:"user"`
|
|
ReceivedAt time.Time `nullable:"true" list:"user" get:"user"`
|
|
EventId string `width:"128" nullable:"true"`
|
|
|
|
SendTimes int
|
|
}
|
|
|
|
const (
|
|
SendByContact = "send_by_contact"
|
|
)
|
|
|
|
func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NotificationCreateInput) (api.NotificationCreateInput, error) {
|
|
cTypes := []string{}
|
|
if len(input.Contacts) > 0 && !userCred.HasSystemAdminPrivilege() {
|
|
return input, httperrors.NewForbiddenError("only admin can send notification by contact")
|
|
}
|
|
|
|
// check robot
|
|
robots := []string{}
|
|
for i := range input.Robots {
|
|
_robot, err := validators.ValidateModel(ctx, userCred, RobotManager, &input.Robots[i])
|
|
if err != nil && !input.IgnoreNonexistentReceiver {
|
|
return input, err
|
|
}
|
|
if _robot != nil {
|
|
robot := _robot.(*SRobot)
|
|
if !utils.IsInStringArray(robot.GetId(), robots) {
|
|
robots = append(robots, robot.GetId())
|
|
}
|
|
if !utils.IsInStringArray(robot.Type, cTypes) {
|
|
cTypes = append(cTypes, robot.Type)
|
|
}
|
|
}
|
|
}
|
|
input.Robots = robots
|
|
|
|
// check receivers
|
|
receivers, err := ReceiverManager.FetchByIdOrNames(ctx, input.Receivers...)
|
|
if err != nil {
|
|
return input, errors.Wrap(err, "ReceiverManager.FetchByIDs")
|
|
}
|
|
idSet := sets.NewString()
|
|
nameSet := sets.NewString()
|
|
for i := range receivers {
|
|
idSet.Insert(receivers[i].Id)
|
|
nameSet.Insert(receivers[i].Name)
|
|
}
|
|
for _, re := range input.Receivers {
|
|
if idSet.Has(re) || nameSet.Has(re) {
|
|
continue
|
|
}
|
|
if input.ContactType == api.WEBCONSOLE {
|
|
input.Contacts = append(input.Contacts, re)
|
|
}
|
|
if !input.IgnoreNonexistentReceiver {
|
|
return input, httperrors.NewInputParameterError("no such receiver whose uid is %q", re)
|
|
}
|
|
}
|
|
input.Receivers = idSet.UnsortedList()
|
|
if len(input.Receivers)+len(input.Contacts)+len(input.Robots) == 0 {
|
|
return input, httperrors.NewInputParameterError("no valid receiver or contact")
|
|
}
|
|
input.ContactType = strings.Join(cTypes, ",")
|
|
nowStr := time.Now().Format("2006-01-02 15:04:05")
|
|
if len(input.Priority) == 0 {
|
|
input.Priority = api.NOTIFICATION_PRIORITY_NORMAL
|
|
}
|
|
// hack
|
|
length := 10
|
|
topicRunes := []rune(input.Topic)
|
|
if len(topicRunes) < 10 {
|
|
length = len(topicRunes)
|
|
}
|
|
name := fmt.Sprintf("%s-%s-%s", string(topicRunes[:length]), input.ContactType, nowStr)
|
|
input.Name, err = db.GenerateName(ctx, nm, ownerId, name)
|
|
if err != nil {
|
|
return input, errors.Wrapf(err, "unable to generate name for %s", name)
|
|
}
|
|
return input, nil
|
|
}
|
|
|
|
func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
|
n.ReceivedAt = time.Now()
|
|
n.Id = db.DefaultUUIDGenerator()
|
|
var input api.NotificationCreateInput
|
|
err := data.Unmarshal(&input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := range input.Receivers {
|
|
_, err := ReceiverNotificationManager.Create(ctx, userCred, input.Receivers[i], 0, n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.Create")
|
|
}
|
|
}
|
|
for i := range input.Contacts {
|
|
_, err := ReceiverNotificationManager.CreateContact(ctx, userCred, input.Contacts[i], n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.CreateContact")
|
|
}
|
|
}
|
|
for i := range input.Robots {
|
|
_, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, input.Robots[i], 0, n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *SNotification) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
|
n.SStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
|
|
n.SetStatus(ctx, userCred, api.NOTIFICATION_STATUS_RECEIVED, "")
|
|
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
|
|
if err != nil {
|
|
n.SetStatus(ctx, userCred, api.NOTIFICATION_STATUS_FAILED, "NewTask")
|
|
return
|
|
}
|
|
task.ScheduleRun(nil)
|
|
}
|
|
|
|
// TODO: support project and domain
|
|
func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.NotificationManagerEventNotifyInput) (api.NotificationManagerEventNotifyOutput, error) {
|
|
var output api.NotificationManagerEventNotifyOutput
|
|
// contact type
|
|
contactTypes := input.ContactTypes
|
|
cts, err := ConfigManager.allContactType(userCred.GetProjectDomainId())
|
|
if err != nil {
|
|
return output, errors.Wrap(err, "unable to fetch allContactType")
|
|
}
|
|
if len(contactTypes) == 0 {
|
|
contactTypes = append(contactTypes, cts...)
|
|
}
|
|
|
|
topic, err := TopicManager.TopicByEvent(input.Event)
|
|
if err != nil {
|
|
return output, errors.Wrapf(err, "TopicByEvent")
|
|
}
|
|
receiverIds := make(map[string]uint32)
|
|
receiverIds1, err := SubscriberManager.getReceiversSent(ctx, topic.Id, input.ProjectDomainId, input.ProjectId)
|
|
if err != nil {
|
|
return output, errors.Wrap(err, "unable to get receive")
|
|
}
|
|
for k, v := range receiverIds1 {
|
|
receiverIds[k] = v
|
|
}
|
|
// robot
|
|
robots := make(map[string]uint32)
|
|
_robots, err := SubscriberManager.robot(topic.Id, input.ProjectDomainId, input.ProjectId)
|
|
if err != nil {
|
|
if errors.Cause(err) != errors.ErrNotFound {
|
|
return output, errors.Wrapf(err, "unable fetch robot of subscription %q", topic.Id)
|
|
}
|
|
} else {
|
|
for robot, groupTime := range _robots {
|
|
robots[robot] = groupTime
|
|
}
|
|
}
|
|
for _, id := range input.RobotIds {
|
|
robots[id] = 0
|
|
}
|
|
|
|
var webhookRobots []string
|
|
realRobot := make(map[string]uint32)
|
|
if len(robots) > 0 {
|
|
robotList := []string{}
|
|
for robot := range robots {
|
|
robotList = append(robotList, robot)
|
|
}
|
|
rs, err := RobotManager.FetchByIdOrNames(ctx, robotList...)
|
|
if err != nil {
|
|
return output, errors.Wrap(err, "unable to get robots")
|
|
}
|
|
|
|
webhookRobots = make([]string, 0, 1)
|
|
for i := range rs {
|
|
if rs[i].Type == api.ROBOT_TYPE_WEBHOOK {
|
|
webhookRobots = append(webhookRobots, rs[i].Id)
|
|
} else {
|
|
realRobot[rs[i].Id] = robots[rs[i].Id]
|
|
}
|
|
}
|
|
}
|
|
|
|
message := jsonutils.Marshal(input.ResourceDetails).String()
|
|
|
|
for _, receiver := range input.ReceiverIds {
|
|
// receiverIds = append(receiverIds, api.SReceiverWithGroupTimes{ReceiverId: receiver})
|
|
if _, ok := receiverIds[receiver]; !ok {
|
|
receiverIds[receiver] = 0
|
|
}
|
|
}
|
|
|
|
receiverIdList := []string{}
|
|
for k := range receiverIds {
|
|
receiverIdList = append(receiverIdList, k)
|
|
}
|
|
receivers, err := ReceiverManager.FetchByIdOrNames(ctx, receiverIdList...)
|
|
if err != nil {
|
|
return output, errors.Wrap(err, "fetch receiver")
|
|
}
|
|
webconsoleContacts := sets.NewString()
|
|
idSet := sets.NewString()
|
|
for i := range receivers {
|
|
idSet.Insert(receivers[i].Id)
|
|
}
|
|
for re := range receiverIds {
|
|
webconsoleContacts.Insert(re)
|
|
}
|
|
|
|
realReceiverIds := make(map[string]uint32)
|
|
for _, id := range receiverIdList {
|
|
realReceiverIds[id] = receiverIds[id]
|
|
}
|
|
// create event
|
|
event, err := EventManager.CreateEvent(ctx, input.Event, topic.Id, message, string(input.Action), input.ResourceType, input.AdvanceDays)
|
|
if err != nil {
|
|
return output, errors.Wrap(err, "unable to create Event")
|
|
}
|
|
|
|
if nm.needWebconsole([]STopic{*topic}) {
|
|
// webconsole
|
|
err = nm.create(ctx, userCred, api.WEBCONSOLE, realReceiverIds, webconsoleContacts.UnsortedList(), input.Priority, event.GetId(), topic.GetId(), topic.Type)
|
|
if err != nil {
|
|
output.FailedList = append(output.FailedList, api.FailedElem{
|
|
ContactType: api.WEBCONSOLE,
|
|
Reason: err.Error(),
|
|
})
|
|
}
|
|
}
|
|
// normal contact type
|
|
for _, ct := range contactTypes {
|
|
if ct == api.MOBILE {
|
|
continue
|
|
}
|
|
err := nm.create(ctx, userCred, ct, realReceiverIds, nil, input.Priority, event.GetId(), topic.GetId(), topic.Type)
|
|
if err != nil {
|
|
log.Errorf("unable to create notification for %s: %v", ct, err)
|
|
output.FailedList = append(output.FailedList, api.FailedElem{
|
|
ContactType: ct,
|
|
Reason: err.Error(),
|
|
})
|
|
}
|
|
}
|
|
err = nm.createWithWebhookRobots(ctx, userCred, webhookRobots, input.Priority, event.GetId(), topic.Type)
|
|
if err != nil {
|
|
log.Errorf("unable to create notification for webhook robots: %v", err)
|
|
output.FailedList = append(output.FailedList, api.FailedElem{
|
|
ContactType: api.WEBHOOK,
|
|
Reason: err.Error(),
|
|
})
|
|
}
|
|
// robot
|
|
err = nm.createWithRobots(ctx, userCred, realRobot, input.Priority, event.GetId(), topic.Type)
|
|
if err != nil {
|
|
log.Errorf("unable to create notification for robots: %v", err)
|
|
output.FailedList = append(output.FailedList, api.FailedElem{
|
|
ContactType: api.ROBOT,
|
|
Reason: err.Error(),
|
|
})
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
func (nm *SNotificationManager) PerformContactNotify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.NotificationManagerContactNotifyInput) (api.NotificationManagerEventNotifyOutput, error) {
|
|
var output api.NotificationManagerEventNotifyOutput
|
|
|
|
params := api.SendParams{
|
|
Title: input.Subject,
|
|
Message: input.Body,
|
|
EmailMsg: api.SEmailMessage{
|
|
Body: input.Body,
|
|
},
|
|
DomainId: userCred.GetDomainId(),
|
|
}
|
|
// 机器人订阅
|
|
if len(input.RobotIds) > 0 {
|
|
robots := []SRobot{}
|
|
q := RobotManager.Query().In("id", input.RobotIds)
|
|
err := db.FetchModelObjects(RobotManager, q, &robots)
|
|
if err != nil {
|
|
output.FailedList = append(output.FailedList, api.FailedElem{ContactType: "robot", Reason: errors.Wrapf(err, "unable to fetch robots:%s", jsonutils.Marshal(input.RobotIds).String()).Error()})
|
|
return output, errors.Wrapf(err, "unable to fetch robots:%s", jsonutils.Marshal(input.RobotIds).String())
|
|
}
|
|
for _, robot := range robots {
|
|
go func(ctx context.Context, userCred mcclient.TokenCredential, robot SRobot, params api.SendParams) {
|
|
params.Header = robot.Header
|
|
params.Body = robot.Body
|
|
params.MsgKey = robot.MsgKey
|
|
params.SecretKey = robot.SecretKey
|
|
params.Receivers = api.SNotifyReceiver{
|
|
Contact: robot.Address,
|
|
}
|
|
driver := GetDriver(fmt.Sprintf("%s-robot", robot.Type))
|
|
err = driver.Send(ctx, params)
|
|
if err != nil {
|
|
logclient.AddSimpleActionLog(&robot, "contact send", err, userCred, false)
|
|
}
|
|
}(ctx, userCred, robot, params)
|
|
}
|
|
}
|
|
// 传入接受人id声明map保证唯一
|
|
receivermap := map[string]struct{}{}
|
|
for _, receiverId := range input.ReceiverIds {
|
|
receivermap[receiverId] = struct{}{}
|
|
}
|
|
// 存在角色接受人
|
|
if len(input.RoleIds) > 0 {
|
|
s := auth.GetAdminSession(ctx, options.Options.Region)
|
|
query := jsonutils.NewDict()
|
|
query.Set("roles", jsonutils.NewStringArray(input.RoleIds))
|
|
query.Set("effective", jsonutils.JSONTrue)
|
|
listRet, err := modules.RoleAssignments.List(s, query)
|
|
if err != nil {
|
|
return output, errors.Wrap(err, "unable to list RoleAssignments")
|
|
}
|
|
userList := []struct {
|
|
User struct {
|
|
Id string `json:"id"`
|
|
} `json:"user"`
|
|
}{}
|
|
jsonutils.Update(&userList, listRet.Data)
|
|
for _, user := range userList {
|
|
receivermap[user.User.Id] = struct{}{}
|
|
}
|
|
}
|
|
// 声明接受人数组
|
|
receiverIds := []string{}
|
|
// 输入接受人与角色去重
|
|
for receiverId := range receivermap {
|
|
receiverIds = append(receiverIds, receiverId)
|
|
}
|
|
// 接受人ID存在的情况下
|
|
if len(receiverIds) > 0 {
|
|
receivers, err := ReceiverManager.FetchByIDs(ctx, receiverIds...)
|
|
if err != nil {
|
|
return output, errors.Wrap(err, "FetchByIDs")
|
|
}
|
|
// 对于每个接受人根据通知渠道逐一发送
|
|
for _, receiver := range receivers {
|
|
// 用户没有启用的情况
|
|
if receiver.Enabled.IsNone() {
|
|
continue
|
|
}
|
|
// 获取启用的通知渠道
|
|
enabledContactTypes, err := receiver.GetEnabledContactTypes()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, contactType := range input.ContactTypes {
|
|
// 通知渠道没有启用
|
|
if !utils.IsInStringArray(contactType, enabledContactTypes) {
|
|
continue
|
|
}
|
|
// 发送
|
|
go func(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiver SReceiver, params api.SendParams) {
|
|
contact, _ := receiver.GetContact(contactType)
|
|
params.Receivers = api.SNotifyReceiver{Contact: contact}
|
|
driver := GetDriver(contactType)
|
|
err = driver.Send(ctx, params)
|
|
if err != nil {
|
|
logclient.AddSimpleActionLog(&receiver, "contact send", err, userCred, false)
|
|
}
|
|
}(ctx, userCred, contactType, receiver, params)
|
|
}
|
|
}
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
func (nm *SNotificationManager) needWebconsole(topics []STopic) bool {
|
|
for i := range topics {
|
|
if topics[i].WebconsoleDisable.IsFalse() || topics[i].WebconsoleDisable.IsNone() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds map[string]uint32, contacts []string, priority, eventId, topicId string, topicType string) error {
|
|
if len(receiverIds)+len(contacts) == 0 {
|
|
return nil
|
|
}
|
|
|
|
n := &SNotification{
|
|
ContactType: contactType,
|
|
Priority: priority,
|
|
ReceivedAt: time.Now(),
|
|
EventId: eventId,
|
|
TopicType: topicType,
|
|
TopicId: topicId,
|
|
}
|
|
n.Id = db.DefaultUUIDGenerator()
|
|
err := nm.TableSpec().Insert(ctx, n)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to insert Notification")
|
|
}
|
|
for receiver := range receiverIds {
|
|
_, err := ReceiverNotificationManager.Create(ctx, userCred, receiver, receiverIds[receiver], n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.Create")
|
|
}
|
|
}
|
|
for i := range contacts {
|
|
_, err := ReceiverNotificationManager.CreateContact(ctx, userCred, contacts[i], n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.CreateContact")
|
|
}
|
|
}
|
|
n.SetModelManager(nm, n)
|
|
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
|
|
if err != nil {
|
|
return errors.Wrapf(err, "NewTask")
|
|
}
|
|
return task.ScheduleRun(nil)
|
|
}
|
|
|
|
func (nm *SNotificationManager) createWithWebhookRobots(ctx context.Context, userCred mcclient.TokenCredential, webhookRobotIds []string, priority, eventId string, topicType string) error {
|
|
if len(webhookRobotIds) == 0 {
|
|
return nil
|
|
}
|
|
n := &SNotification{
|
|
ContactType: api.WEBHOOK,
|
|
Priority: priority,
|
|
ReceivedAt: time.Now(),
|
|
EventId: eventId,
|
|
TopicType: topicType,
|
|
}
|
|
n.Id = db.DefaultUUIDGenerator()
|
|
for i := range webhookRobotIds {
|
|
_, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, webhookRobotIds[i], 0, n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot")
|
|
}
|
|
}
|
|
err := nm.TableSpec().Insert(ctx, n)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to insert Notification")
|
|
}
|
|
n.SetModelManager(nm, n)
|
|
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
|
|
if err != nil {
|
|
return errors.Wrapf(err, "NewTask")
|
|
}
|
|
return task.ScheduleRun(nil)
|
|
}
|
|
|
|
func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds map[string]uint32, priority, eventId string, topicType string) error {
|
|
if len(robotIds) == 0 {
|
|
return nil
|
|
}
|
|
n := &SNotification{
|
|
ContactType: api.ROBOT,
|
|
Priority: priority,
|
|
ReceivedAt: time.Now(),
|
|
EventId: eventId,
|
|
TopicType: topicType,
|
|
}
|
|
n.Id = db.DefaultUUIDGenerator()
|
|
for i := range robotIds {
|
|
_, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, i, robotIds[i], n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot")
|
|
}
|
|
}
|
|
err := nm.TableSpec().Insert(ctx, n)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to insert Notification")
|
|
}
|
|
n.SetModelManager(nm, n)
|
|
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
|
|
if err != nil {
|
|
log.Errorf("NotificationSendTask newTask error %v", err)
|
|
} else {
|
|
task.ScheduleRun(nil)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *SNotification) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverIds map[string]uint32, contacts []string) error {
|
|
if len(receiverIds)+len(contacts) == 0 {
|
|
return nil
|
|
}
|
|
|
|
n.Id = db.DefaultUUIDGenerator()
|
|
err := NotificationManager.TableSpec().Insert(ctx, n)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to insert Notification")
|
|
}
|
|
for receiver := range receiverIds {
|
|
_, err := ReceiverNotificationManager.Create(ctx, userCred, receiver, receiverIds[receiver], n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.Create")
|
|
}
|
|
}
|
|
for i := range contacts {
|
|
_, err := ReceiverNotificationManager.CreateContact(ctx, userCred, contacts[i], n.Id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ReceiverNotificationManager.CreateContact")
|
|
}
|
|
}
|
|
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
|
|
if err != nil {
|
|
log.Errorf("NotificationSendTask newTask error %v", err)
|
|
} else {
|
|
task.ScheduleRun(nil)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (nm *SNotificationManager) FetchCustomizeColumns(
|
|
ctx context.Context,
|
|
userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject,
|
|
objs []interface{},
|
|
fields stringutils2.SSortedStrings,
|
|
isList bool,
|
|
) []api.NotificationDetails {
|
|
rows := make([]api.NotificationDetails, len(objs))
|
|
resRows := nm.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
|
|
var err error
|
|
notifications := make([]*SNotification, len(objs))
|
|
for i := range notifications {
|
|
notifications[i] = objs[i].(*SNotification)
|
|
}
|
|
|
|
for i := range rows {
|
|
rows[i], err = notifications[i].getMoreDetails(ctx, userCred, query, rows[i])
|
|
if err != nil {
|
|
log.Errorf("Notification.getMoreDetails: %v", err)
|
|
}
|
|
rows[i].StatusStandaloneResourceDetails = resRows[i]
|
|
}
|
|
return rows
|
|
}
|
|
|
|
func (n *SNotification) ReceiverNotificationsNotOK() ([]SReceiverNotification, error) {
|
|
rnq := ReceiverNotificationManager.Query().Equals("notification_id", n.Id).NotEquals("status", api.RECEIVER_NOTIFICATION_OK)
|
|
rns := make([]SReceiverNotification, 0, 1)
|
|
err := db.FetchModelObjects(ReceiverNotificationManager, rnq, &rns)
|
|
if err == sql.ErrNoRows {
|
|
return []SReceiverNotification{}, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rns, nil
|
|
}
|
|
|
|
func (n *SNotification) receiveDetails(userCred mcclient.TokenCredential, scope string) ([]api.ReceiveDetail, error) {
|
|
RQ := ReceiverManager.Query("id", "name")
|
|
q := ReceiverNotificationManager.Query("receiver_id", "notification_id", "receiver_type", "contact", "send_at", "send_by", "status", "failed_reason").Equals("notification_id", n.Id).IsNotEmpty("receiver_id").IsNullOrEmpty("contact")
|
|
s := rbacscope.TRbacScope(scope)
|
|
|
|
switch s {
|
|
case rbacscope.ScopeSystem:
|
|
subRQ := RQ.SubQuery()
|
|
q.AppendField(subRQ.Field("name", "receiver_name"))
|
|
q = q.LeftJoin(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id"))))
|
|
case rbacscope.ScopeDomain:
|
|
subRQ := RQ.Equals("domain_id", userCred.GetDomainId()).SubQuery()
|
|
q.AppendField(subRQ.Field("name", "receiver_name"))
|
|
q = q.Join(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id"))))
|
|
default:
|
|
subRQ := RQ.Equals("id", userCred.GetUserId()).SubQuery()
|
|
q.AppendField(subRQ.Field("name", "receiver_name"))
|
|
q = q.Join(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id"))))
|
|
}
|
|
ret := make([]api.ReceiveDetail, 0, 2)
|
|
err := q.All(&ret)
|
|
if err != nil && errors.Cause(err) != sql.ErrNoRows {
|
|
log.Errorf("SQuery.All: %v", err)
|
|
return nil, err
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (n *SNotification) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) {
|
|
// get title adn content
|
|
lang := getLangSuffix(ctx)
|
|
nn, err := n.Notification(false)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
p, err := n.GetTemplate(ctx, n.TopicId, lang, nn)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
out.Title = p.Title
|
|
out.Content = p.Message
|
|
|
|
scope, _ := query.GetString("scope")
|
|
// get receive details
|
|
out.ReceiveDetails, err = n.receiveDetails(userCred, scope)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (n *SNotification) Notification(robotUseTemplate bool) (api.SsNotification, error) {
|
|
if n.EventId == "" {
|
|
return api.SsNotification{
|
|
ContactType: n.ContactType,
|
|
Topic: n.Topic,
|
|
Message: n.Message,
|
|
}, nil
|
|
}
|
|
event, err := EventManager.GetEvent(n.EventId)
|
|
if err != nil {
|
|
return api.SsNotification{}, err
|
|
}
|
|
e, _ := parseEvent(event.Event)
|
|
return api.SsNotification{
|
|
ContactType: n.ContactType,
|
|
Topic: n.Topic,
|
|
Message: event.Message,
|
|
Event: e,
|
|
AdvanceDays: event.AdvanceDays,
|
|
RobotUseTemplate: robotUseTemplate,
|
|
}, nil
|
|
}
|
|
|
|
func (nm *SNotificationManager) ResourceScope() rbacscope.TRbacScope {
|
|
return rbacscope.ScopeUser
|
|
}
|
|
|
|
func (nm *SNotificationManager) NamespaceScope() rbacscope.TRbacScope {
|
|
return rbacscope.ScopeSystem
|
|
}
|
|
|
|
func (nm *SNotificationManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
|
|
return db.FetchUserInfo(ctx, data)
|
|
}
|
|
|
|
func (nm *SNotificationManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
|
|
if owner == nil {
|
|
return q
|
|
}
|
|
switch scope {
|
|
case rbacscope.ScopeDomain:
|
|
subRq := ReceiverManager.Query("id").Equals("domain_id", owner.GetDomainId()).SubQuery()
|
|
RNq := ReceiverNotificationManager.Query("notification_id", "receiver_id")
|
|
subRNq := RNq.Join(subRq, sqlchemy.OR(
|
|
sqlchemy.Equals(RNq.Field("receiver_id"), subRq.Field("id")),
|
|
sqlchemy.Equals(RNq.Field("contact"), subRq.Field("id")),
|
|
)).SubQuery()
|
|
q = q.Join(subRNq, sqlchemy.Equals(q.Field("id"), subRNq.Field("notification_id")))
|
|
case rbacscope.ScopeProject, rbacscope.ScopeUser:
|
|
sq := ReceiverNotificationManager.Query("notification_id")
|
|
subq := sq.Filter(sqlchemy.OR(
|
|
sqlchemy.Equals(sq.Field("receiver_id"), owner.GetUserId()),
|
|
sqlchemy.Equals(sq.Field("contact"), owner.GetUserId()),
|
|
)).SubQuery()
|
|
q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id")))
|
|
}
|
|
return q
|
|
}
|
|
|
|
func (n *SNotification) AddOne() error {
|
|
_, err := db.Update(n, func() error {
|
|
n.SendTimes += 1
|
|
return nil
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (nm *SNotificationManager) InitializeData() error {
|
|
return dataCleaning(nm.TableSpec().Name())
|
|
}
|
|
|
|
func dataCleaning(tableName string) error {
|
|
now := time.Now()
|
|
monthsDaysAgo := now.AddDate(0, -1, 0).Format("2006-01-02 15:04:05")
|
|
sqlStr := fmt.Sprintf(
|
|
"delete from %s where deleted = 0 and created_at < '%s'",
|
|
tableName,
|
|
monthsDaysAgo,
|
|
)
|
|
q := sqlchemy.NewRawQuery(sqlStr)
|
|
rows, err := q.Rows()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to delete expired data in %q", tableName)
|
|
}
|
|
defer rows.Close()
|
|
log.Infof("delete expired data in %q successfully", tableName)
|
|
return nil
|
|
}
|
|
|
|
// 通知消息列表
|
|
func (nm *SNotificationManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.NotificationListInput) (*sqlchemy.SQuery, error) {
|
|
q, err := nm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(input.ContactType) > 0 {
|
|
q = q.Equals("contact_type", input.ContactType)
|
|
}
|
|
if len(input.ReceiverId) > 0 {
|
|
subq := ReceiverNotificationManager.Query("notification_id").Equals("receiver_id", input.ReceiverId).SubQuery()
|
|
q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id")))
|
|
}
|
|
if len(input.Tag) > 0 {
|
|
q = q.Equals("tag", input.Tag)
|
|
}
|
|
if len(input.TopicType) > 0 {
|
|
q = q.Equals("topic_type", input.TopicType)
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
func (nm *SNotificationManager) ReSend(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
|
|
timeLimit := time.Now().Add(-time.Duration(options.Options.ReSendScope) * time.Second * 2).Format("2006-01-02 15:04:05")
|
|
q := nm.Query().GT("created_at", timeLimit).In("status", []string{api.NOTIFICATION_STATUS_FAILED, api.NOTIFICATION_STATUS_PART_OK}).LT("send_times", options.Options.MaxSendTimes)
|
|
ns := make([]SNotification, 0, 2)
|
|
err := db.FetchModelObjects(nm, q, &ns)
|
|
if err != nil {
|
|
log.Errorf("fail to FetchModelObjects: %v", err)
|
|
return
|
|
}
|
|
log.Infof("need to resend total %d notifications", len(ns))
|
|
for i := range ns {
|
|
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", &ns[i], userCred, nil, "", "")
|
|
if err != nil {
|
|
log.Errorf("NotificationSendTask newTask error %v", err)
|
|
} else {
|
|
task.ScheduleRun(nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *SNotification) GetNotOKReceivers() ([]SReceiver, error) {
|
|
ret := []SReceiver{}
|
|
q := ReceiverManager.Query().IsTrue("enabled")
|
|
sq := ReceiverNotificationManager.Query().Equals("notification_id", n.Id).NotEquals("status", api.RECEIVER_NOTIFICATION_OK).Equals("receiver_type", api.RECEIVER_TYPE_USER).SubQuery()
|
|
q = q.Join(sq, sqlchemy.Equals(q.Field("id"), sq.Field("receiver_id")))
|
|
err := db.FetchModelObjects(ReceiverManager, q, &ret)
|
|
return ret, err
|
|
}
|
|
|
|
func (n *SNotification) TaskInsert() error {
|
|
return NotificationManager.TableSpec().Insert(context.Background(), n)
|
|
}
|
|
|
|
// 获取消息文案
|
|
func (n *SNotification) GetTemplate(ctx context.Context, topicId, lang string, no api.SsNotification) (api.SendParams, error) {
|
|
if len(n.EventId) == 0 || n.ContactType == api.MOBILE {
|
|
return TemplateManager.FillWithTemplate(ctx, lang, no)
|
|
}
|
|
|
|
out, event := api.SendParams{}, no.Event
|
|
topicModel, err := TopicManager.FetchById(topicId)
|
|
if err != nil {
|
|
return out, errors.Wrapf(err, "get topic by id")
|
|
}
|
|
topic := topicModel.(*STopic)
|
|
groupKeys := []string{}
|
|
if topic.GroupKeys != nil {
|
|
groupKeys = *topic.GroupKeys
|
|
}
|
|
|
|
rtStr, aStr, resultStr := event.ResourceType(), string(event.Action()), string(event.Result())
|
|
msgObj, err := jsonutils.ParseString(no.Message)
|
|
if err != nil {
|
|
return out, errors.Wrapf(err, "unable to parse json from %q", no.Message)
|
|
}
|
|
msg := msgObj.(*jsonutils.JSONDict)
|
|
if !msg.Contains("brand") {
|
|
if info, _ := TemplateManager.GetCompanyInfo(ctx); len(info.Name) > 0 {
|
|
msg.Set("brand", jsonutils.NewString(info.Name))
|
|
}
|
|
}
|
|
webhookMsg := jsonutils.NewDict()
|
|
webhookMsg.Set("resource_type", jsonutils.NewString(rtStr))
|
|
webhookMsg.Set("action", jsonutils.NewString(aStr))
|
|
webhookMsg.Set("result", jsonutils.NewString(resultStr))
|
|
webhookMsg.Set("resource_details", msg)
|
|
|
|
if (no.ContactType == api.WEBHOOK || no.ContactType == api.WEBHOOK_ROBOT) && !no.RobotUseTemplate {
|
|
return api.SendParams{
|
|
Title: no.Event.StringWithDeli("_"),
|
|
Message: webhookMsg.String(),
|
|
}, nil
|
|
}
|
|
for _, key := range groupKeys {
|
|
keyValue, _ := msg.GetString(key)
|
|
if len(keyValue) > 0 {
|
|
out.GroupKey += keyValue
|
|
}
|
|
}
|
|
if lang == "" {
|
|
lang = getLangSuffix(ctx)
|
|
}
|
|
|
|
// 文案关键字翻译
|
|
tag := languageTag(lang)
|
|
rtDis := notifyclientI18nTable.LookupByLang(tag, rtStr)
|
|
if len(rtDis) == 0 {
|
|
rtDis = rtStr
|
|
}
|
|
aDis := notifyclientI18nTable.LookupByLang(tag, aStr)
|
|
if len(aDis) == 0 {
|
|
aDis = aStr
|
|
}
|
|
resultDis := notifyclientI18nTable.LookupByLang(tag, resultStr)
|
|
if len(resultDis) == 0 {
|
|
resultDis = resultStr
|
|
}
|
|
templateParams := webhookMsg
|
|
templateParams.Set("advance_days", jsonutils.NewInt(int64(no.AdvanceDays)))
|
|
templateParams.Set("resource_type_display", jsonutils.NewString(rtDis))
|
|
templateParams.Set("action_display", jsonutils.NewString(aDis))
|
|
templateParams.Set("result_display", jsonutils.NewString(resultDis))
|
|
|
|
var stemplateTitle *template.Template
|
|
var stemplateContent *template.Template
|
|
|
|
failedReason := []error{}
|
|
switch lang {
|
|
case api.TEMPLATE_LANG_CN:
|
|
stemplateTitle, err = template.New("template").Parse(topic.TitleCn)
|
|
if err != nil {
|
|
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
|
|
failedReason = append(failedReason, errors.Wrapf(err, "parse title cn %s", topic.TitleCn))
|
|
}
|
|
stemplateContent, err = template.New("template").Parse(topic.ContentCn)
|
|
if err != nil {
|
|
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
|
|
failedReason = append(failedReason, errors.Wrapf(err, "parse content cn %s", topic.ContentCn))
|
|
}
|
|
case api.TEMPLATE_LANG_EN:
|
|
stemplateTitle, err = template.New("template").Parse(topic.TitleEn)
|
|
if err != nil {
|
|
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_EN)
|
|
failedReason = append(failedReason, errors.Wrapf(err, "parse title en %s", topic.TitleEn))
|
|
}
|
|
stemplateContent, err = template.New("template").Parse(topic.ContentEn)
|
|
if err != nil {
|
|
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
|
|
failedReason = append(failedReason, errors.Wrapf(err, "parse content en %s", topic.ContentEn))
|
|
}
|
|
default:
|
|
failedReason = append(failedReason, errors.Errorf("empty lang"))
|
|
stemplateTitle, err = template.New("template").Parse(topic.TitleEn)
|
|
if err != nil {
|
|
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_EN)
|
|
failedReason = append(failedReason, errors.Wrapf(err, "parse topic en %s", topic.TitleEn))
|
|
}
|
|
stemplateContent, err = template.New("template").Parse(topic.ContentEn)
|
|
if err != nil {
|
|
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
|
|
failedReason = append(failedReason, errors.Wrapf(err, "parse content en: %s", topic.ContentEn))
|
|
}
|
|
}
|
|
if len(failedReason) > 0 {
|
|
return out, errors.NewAggregate(failedReason)
|
|
}
|
|
tmpTitle := strings.Builder{}
|
|
tmpContent := strings.Builder{}
|
|
err = stemplateTitle.Execute(&tmpTitle, templateParams.Interface())
|
|
if err != nil {
|
|
failedReason = append(failedReason, errors.Errorf("unable to stemplateTitle.Execute:%s", err.Error()))
|
|
}
|
|
err = stemplateContent.Execute(&tmpContent, templateParams.Interface())
|
|
if err != nil {
|
|
failedReason = append(failedReason, errors.Errorf("unable to stemplateContent.Execute:%s", err.Error()))
|
|
}
|
|
out.Title = html.UnescapeString(tmpTitle.String())
|
|
out.Message = html.UnescapeString(tmpContent.String())
|
|
return out, errors.NewAggregate(failedReason)
|
|
}
|