fix(notify): reset notifications

This commit is contained in:
马鸿飞
2023-04-18 15:11:17 +08:00
parent 699d58fcb9
commit b01b44054e
9 changed files with 733 additions and 574 deletions
+2
View File
@@ -111,6 +111,7 @@ func init() {
Action string
Contacts string
IsFailed string
AdvanceDays int
}
R(&NotificationEventInput{}, "notify-event-send", "Send notify event message", func(s *mcclient.ClientSession, args *NotificationEventInput) error {
body, err := jsonutils.ParseString(args.MsgBody)
@@ -125,6 +126,7 @@ func init() {
ReceiverIds: []string{},
ResourceDetails: dict,
Event: args.Event,
AdvanceDays: args.AdvanceDays,
Priority: args.Priority,
ResourceType: args.ResourceType,
Action: api.SAction(args.Action),
+409
View File
@@ -93,3 +93,412 @@ type TemplateDetails struct {
STemplate
}
// 密码即将失效通知
const (
PWD_EXPIRE_SOON_TITLE_CN = `{{- $d := .resource_details -}}
{{ $d.account }}:您的密码有效期将过`
PWD_EXPIRE_SOON_TITLE_EN = `{{- $d := .resource_details -}}
{{ $d.account }}:Your password is valid and will expire soon`
PWD_EXPIRE_SOON_CONTENT_CN = `{{- $d := .resource_details -}}
{{ $d.account }}:您的密码有效期将过,请及时登录平台更新密码。`
PWD_EXPIRE_SOON_CONTENT_EN = `{{- $d := .resource_details -}}
{{ $d.account }}:Your password is valid and will expire soon. Please log in to the platform in time to update your password.`
)
// 资源即将到期通知
const (
EXPIRED_RELEASE_TITLE_CN = `{{- $d := .resource_details -}}
{{ $d.project }}项目的
{{ .resource_type_display }}{{ $d.name }}到期前{{ .advance_days }}天通知`
EXPIRED_RELEASE_TITLE_EN = `{{- $d := .resource_details -}}
{{ .advance_days }} days notice before {{ .resource_type }} {{ $d.name }} {{ if $d.project -}} in project {{ $d.project }} {{ end -}} expiration`
EXPIRED_RELEASE_CONTENT_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的
{{- if $d.brand -}}
{{ $d.brand }}平台
{{- end -}}
{{- if $d.private_dns -}}
,内网地址为{{ $d.private_dns }}:{{ $d.private_connect_port }}
{{- end -}}
{{- if $d.public_dns -}}
,外网地址为{{ $d.public_dns }}:{{ $d.public_connect_port }}
{{- end -}}
{{ .resource_type_display }}{{ $d.name }}还有{{ .advance_days }}天就要到期释放,{{ if $d.auto_renew }}到期已开启自动续费,{{ end }}如有其它变更,请尽快前往控制台处理`
EXPIRED_RELEASE_CONTENT_EN = `{{- $d := .resource_details -}}
Your {{ if $d.brand -}} {{ $d.brand }} {{ end -}} {{ .resource_type }} {{ $d.name }} {{ if $d.public_dns -}} with external address {{ $d.public_dns }}:{{ $d.public_connect_port }} {{ end -}} {{ if $d.project -}} in project {{ $d.project }} {{ end -}} will expire and be released in {{ .advance_days }} days. {{ if $d.auto_renew }}It has turned on automatic renewal. {{ end }}If there are other changes, please go to the console as soon as possible.`
)
// 服务崩溃通知
const (
PANIC_TITLE_CN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.task_name }} 崩溃了`
PANIC_TITLE_EN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.task_name }} PANIC`
PANIC_CONTENT_CN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.task_name }} PANIC {{- if $d.error -}} 错误: {{ $d.error }} {{- end -}}
堆栈信息:
{{ $d.stack }}`
PANIC_CONTENT_EN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.task_name }} PANIC {{- if $d.error -}} Error: {{ $d.error }} {{- end -}}
Stack Info:
{{ $d.stack }}`
)
// 日志容量超限通知
const (
ACTION_LOG_EXCEED_COUNT_TITLE_CN = `{{- $d := .resource_details -}}
操作日志超出设置数量{{ $d.exceed_count }}条,当前{{ $d.current_count }}`
ACTION_LOG_EXCEED_COUNT_TITLE_EN = `{{- $d := .resource_details -}}
Action logs excced expected count {{ $d.exceed_count }}, current count is {{ $d.current_count }}`
ACTION_LOG_EXCEED_COUNT_CONTENT_CN = `{{- $d := .resource_details.action -}}
当前日志 ID: {{ $d.id }}`
ACTION_LOG_EXCEED_COUNT_CONTENT_EN = `{{- $d := .resource_details.action -}}
Current log ID: {{ $d.id }}`
)
// 完整性校验失败通知
const (
CHECKSUM_TEST_FAILED_TITLE_CN = `{{- $d := .resource_details -}}
{{- if eq .resource_type "cloudpods_component" }}
{{ $d.title }}
{{- else -}}
{{ .resource_type_display }}完整性校验失败
{{- end -}}`
CHECKSUM_TEST_FAILED_TITLE_EN = `{{- $d := .resource_details -}}
{{- if eq .resource_type "cloudpods_component" }}
{{ $d.title }}
{{- else -}}
The checksum of {{ .resource_type_display }} test failed
{{- end -}}`
CHECKSUM_TEST_FAILED_CONTENT_CN = `{{- $d := .resource_details -}}
{{- if eq .resource_type "db_table_record" }}
{{ $d.table_name }}记录{{ $d.name }}被修改,完整性校验失败。期望校验和({{ $d.expected_checksum }}) != 计算校验和({{ $d.calculated_checksum }})。
{{- end -}}
{{- if eq .resource_type "cloudpods_component" }}
{{ $d.details }}
{{- end -}}
{{- if eq .resource_type "snapshot" }}
快照{{ $d.name }}的内存快照完整性校验失败
{{- end -}}
{{- if eq .resource_type "image" }}
镜像{{ $d.name }}完整性校验失败
{{- end -}}
{{- if eq .resource_type "vm_integrity" }}
主机{{ $d.name }}完整性校验失败
{{- end -}}`
CHECKSUM_TEST_FAILED_CONTENT_EN = `{{- $d := .resource_details -}}
{{- if eq .resource_type "db_table_record" }}
The record {{ $d.name }} in table {{ $d.table_name }} of the database {{ $d.db_name }} has been modified because the checksum test failed. Expected_checksum({{ $d.expected_checksum }}) != Calculated_checksum({{ $d.calculated_checksum }}).
{{- end -}}
{{- if eq .resource_type "cloudpods_component" }}
{{ $d.details }}
{{- end -}}
{{- if eq .resource_type "snapshot" }}
The checksum of the memory snapshot of the snapshot {{ $d.name }} test failed.
{{- end -}}
{{- if eq .resource_type "image" }}
The checksum of the image {{ $d.name }} test failed.
{{- end -}}`
)
// 通用通知
const (
COMMON_TITLE_CN = `{{- $d := .resource_details -}}
{{- if $d.project -}}
{{ $d.project }}项目的
{{- end -}}
{{ .resource_type_display }}{{ $d.name }}{{ .action_display }}{{ .result_display }}`
COMMON_TITLE_EN = `{{- $d := .resource_details -}}
The {{ .resource_type_display }} {{ .Name }} {{ if $d.project }} in poject {{ $d.project }} {{ end -}} {{ .action_display }} {{ .result_display }}`
COMMON_CONTENT_CN = `{{- $d := .resource_details -}}
{{- if $d.project -}}
{{ $d.project }}项目
{{- end -}}
{{- if $d.brand -}}
{{ $d.brand }}平台
{{- end -}}
{{ .resource_type_display }}{{ $d.name }}{{ .action_display }}{{.result_display}}
{{- if eq .result "failed" -}}
,请尽快前往控制台进行处理
{{- end -}}`
COMMON_CONTENT_EN = `{{- $d := .resource_details -}}
Your {{ if $d.brand -}} {{ $d.brand }} {{ end -}} {{ .resource_type_display }} {{ $d.name }} {{ if $d.project -}} in project {{ $d.project }} {{ end -}} has been {{ .action_display }} {{ .result_display }}
{{- if eq .result "failed" -}}
. And please go to the console as soon as possible to process.
{{- end -}}`
)
// 服务组件异常通知
const (
EXCEPTION_TITLE_CN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.name }} 发生异常: {{ $d.message }}`
EXCEPTION_TITLE_EN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.name }} exception occurs: {{ $d.message }}`
EXCEPTION_CONTENT_CN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.name }} 发生异常: {{ $d.message }}`
EXCEPTION_CONTENT_EN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.name }} exception occurs: {{ $d.message }}`
)
// 数据库主从同步不一致通知
const (
MYSQL_OUT_OF_SYNC_TITLE_CN = `{{- $d := .resource_details -}}
数据库 {{$d.ip}} 的主从同步不一致,请及时检查。`
MYSQL_OUT_OF_SYNC_TITLE_EN = `{{- $d := .resource_details -}}
The primary and secondary synchronization of the database ({{ $d.ip }}) is inconsistent, please check in time.`
MYSQL_OUT_OF_SYNC_CONTENT_CN = `{{- $d := .resource_details -}}
数据库 {{$d.ip}} 的主从同步不一致,请及时检查。
{{ range $status := $d.status }}
数据库 {{$status.ip}} 状态:
{{- if not $status.operator_error }}
- Slave_IO_Running: {{$status.slave_io_running}}
- Slave_SQL_Running: {{$status.slave_sql_running}}
{{- if $status.last_error }}
- Last_Error: {{$status.last_error}}
{{- end -}}
{{- if $status.last_io_error }}
- Last_IO_Error: {{$status.last_io_error}}
{{- end }}
{{else}}
- Operator_Error: {{$status.operator_error}}
{{- end}}
{{end}}`
MYSQL_OUT_OF_SYNC_CONTENT_EN = `{{- $d := .resource_details -}}
The primary and secondary synchronization of the database ({{ $d.ip }}) is inconsistent, please check in time.
{{ range $status := $d.status }}
Database {{$status.ip}} status:
{{- if not $status.operator_error }}
- Slave_IO_Running: {{$status.slave_io_running}}
- Slave_SQL_Running: {{$status.slave_sql_running}}
{{- if $status.last_error }}
- Last_Error: {{$status.last_error}}
{{- end -}}
{{- if $status.last_io_error }}
- Last_IO_Error: {{$status.last_io_error}}
{{- end }}
{{else}}
- Operator_Error: {{$status.operator_error}}
{{- end}}
{{end}}`
)
// 网络拓扑不一致通知
const (
NET_OUT_OF_SYNC_TITLE_CN = `{{- $d := .resource_details -}}
{{ $d.service_name }}服务的网络拓扑信息同步不一致,请及时检查。 `
NET_OUT_OF_SYNC_TITLE_EN = `{{- $d := .resource_details -}}
{{ $d.service_name }}: The network topology information of the service is inconsistent, please check in time.`
NET_OUT_OF_SYNC_CONTENT_CN = `{{- $d := .resource_details -}}
{{ $d.service_name }}服务的网络拓扑信息同步不一致,请及时检查。 `
NET_OUT_OF_SYNC_CONTENT_EN = `{{- $d := .resource_details -}}
{{ $d.service_name }}: The network topology information of the service is inconsistent, please check in time.`
)
// 离线通知
const (
OFFLINE_TITLE_CN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.name }} 离线`
OFFLINE_TITLE_EN = `{{- $d := .resource_details -}}
The {{ .resource_type }} {{ $d.name }} Offline `
OFFLINE_CONTENT_CN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.name }} 离线 {{- if $d.reason -}} 原因: {{ $d.reason }} {{- end -}}`
OFFLINE_CONTENT_EN = `{{- $d := .resource_details -}}
The {{ .resource_type }} {{ $d.name }} Offline {{- if $d.reason -}} Reason: {{ $d.reason }}{{- end -}}`
)
// 资源加入回收站通知
const (
PENDING_DELETE_TITLE_CN = `{{- $d := .resource_details -}}
{{- if $d.project -}}
{{ $d.project }}项目的
{{- end -}}
{{ .resource_type_display }}{{ $d.name }}成功加入回收站`
PENDING_DELETE_TITLE_EN = `{{- $d := .resource_details -}}
The {{ .resource_type_display }} {{ .Name }} {{ if $d.project }} in poject {{ $d.project }} {{ end -}} has been added to recycle bin successfully`
PENDING_DELETE_CONTENT_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的
{{- if $d.ips -}}
IP地址为{{ $d.ips }}
{{- end -}}
{{- if $d.ip_addr -}}
IP地址为{{ $d.ip_addr }}
{{- end -}}
{{- if $d.brand -}}
{{ $d.brand }}平台
{{- end -}}
{{ .resource_type_display }}{{ $d.name }}成功加入回收站`
PENDING_DELETE_CONTENT_EN = `{{- $d := .resource_details -}}
Your {{ if $d.brand -}} {{ $d.brand }} {{ end -}} {{ .resource_type_display }} {{ $d.name }}
{{ if $d.project -}} in project {{ $d.project }} {{ end -}} has been added to recycle bin successfully
{{- if $d.private_dns -}}
, the intranet address is {{ $d.private_dns }}:{{ $d.private_connect_port }}
{{- end -}}
{{- if $d.public_dns -}}
, the external address is {{ $d.public_dns }}:{{ $d.public_connect_port }}
{{- end -}}
{{- if and $d.address_type $d.address -}}
, the service address is {{ $d.address_type }}{{ $d.address }}
{{- end -}}
{{- if $d.ips -}}
, the IP address is {{ $d.ips }}
{{- end -}}
{{- if $d.ip_addr -}}
, the IP address is {{ $d.ip_addr }}
{{- end -}}`
)
// 虚拟机崩溃通知
const (
SERVER_PANICKED_TITLE_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的虚拟机{{ $d.name }}崩溃了`
SERVER_PANICKED_TITLE_EN = `{{- $d := .resource_details -}}
{{ .resource_type }} {{ $d.task_name }} PANIC`
SERVER_PANICKED_CONTENT_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的虚拟机{{ $d.name }}崩溃了`
SERVER_PANICKED_CONTENT_EN = `{{- $d := .resource_details -}}
The server {{ $d.name }} in project {{ $d.project }} panicked.`
)
// 资源定时调度任务通知
const (
SCHEDULEDTASK_EXECUTE_TITLE_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的定时任务执行成功`
SCHEDULEDTASK_EXECUTE_TITLE_EN = `{{- $d := .resource_details -}}
The scheduled task in {{ $d.project }} execute successfully`
SCHEDULEDTASK_EXECUTE_CONTENT_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的定时任务执行成功`
SCHEDULEDTASK_EXECUTE_CONTENT_EN = `{{- $d := .resource_details -}}
The scheduled task in {{ $d.project }} successfully {{ $d.operation_display }} the {{ $d.resource_type_display }} {{ $d.resource_name }}`
)
// 服务异常通知
const (
SERVICE_ABNORMAL_TITLE_CN = `{{- $d := .resource_details -}}
服务{{ $d.service_name }}异常`
SERVICE_ABNORMAL_TITLE_EN = `{{- $d := .resource_details -}}
Server {{ $d.service_name }} abnormal`
SERVICE_ABNORMAL_CONTENT_CN = `{{- $d := .resource_details -}}
服务: {{ $d.service_name }} 异常。
方法: {{ $d.method }}
路径: {{ $d.path }}
{{- if $d.body }}
请求: {{ $d.body -}}
{{ end }}
错误: {{ $d.error }}`
SERVICE_ABNORMAL_CONTENT_EN = `{{- $d := .resource_details -}}
Service: {{ $d.service_name }} abnormal
Method: {{ $d.method }}
Path: {{ $d.path }}
{{- if $d.body }}
Body: {{ $d.body -}}
{{ end }}
Error: {{ $d.error }}`
)
// 弹性伸缩组策略生效通知
const (
SCALINGPOLICY_EXECUTE_TITLE_CN = `{{- $d := .resource_details -}}
{{ $d.project }}项目的弹性伸缩组{{ $d.scaling_group }}中的伸缩策略{{ $d.name }}满足触发条件`
SCALINGPOLICY_EXECUTE_TITLE_EN = `{{- $d := .resource_details -}}
The scaling policy {{ $d.name }} in the scaling group {{ $d.scaling_group }} triggered`
SCALINGPOLICY_EXECUTE_CONTENT_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的弹性伸缩组{{ $d.scaling_group }}中的{{ $d.trigger_type_display }}类型伸缩策略{{ $d.name }}满足触发条件,成功{{ $d.action_display }}{{ $d.number }}{{ $d.unit_display }}实例`
SCALINGPOLICY_EXECUTE_CONTENT_EN = `{{- $d := .resource_details -}}
The {{ $d.trigger_type_display }} type scaling policy {{ $d.name }} in the scaling group {{ $d.scaling_group }} of the {{ $d.project }} project satisfies the trigger conditions, and {{ $d.action_display }} {{ $d.number }}{{ $d.unit_display }} instances successfully`
)
// 自动快照策略生效通知
const (
SNAPSHOTPOLICY_EXECUTE_TITLE_CN = `{{- $d := .resource_details -}}
{{ $d.project }}项目的自动快照策略{{ $d.name }}生效`
SNAPSHOTPOLICY_EXECUTE_TITLE_EN = `{{- $d := .resource_details -}}
The snapshot policy {{ $d.name }} in the {{ $d.project }} project executed`
SNAPSHOTPOLICY_EXECUTE_CONTENT_CN = `{{- $d := .resource_details -}}
您在{{ $d.project }}项目的自动快照策略{{ $d.name }}为硬盘{{ $d.disk }}创建快照成功`
SNAPSHOTPOLICY_EXECUTE_CONTENT_EN = `{{- $d := .resource_details -}}
The snapshot policy {{ $d.name }} in the {{ $d.project }} project successfully creates a snapshot for the disk {{ $d.disk }}`
)
// 资源变更通知
const (
UPDATE_TITLE_CN = `{{- $d := .resource_details -}}
if {{ $d.project }}
{{ $d.project }}项目的
{{- end -}}
{{ .resource_type_display }}{{ $d.name }}{{ .action_display }}成功`
UPDATE_TITLE_EN = `{{- $d := .resource_details -}}
The {{ .resource_type }} {{ $d.name }} {{ if $d.project -}} in project {{ $d.project }} {{ end -}} {{ .action_display }} successfully`
UPDATE_CONTENT_CN = `{{- $d := .resource_details -}}
if {{$d.project }}
{{ $d.project }}项目
{{- end -}}
{{- if $d.brand -}}
{{ $d.brand }}平台
{{- end -}}
{{ .resource_type_display }}{{ $d.name }}{{ .action_display }}成功
{{- if $d.account -}}
,帐号为{{ $d.account }}
{{- end -}}
{{- if $d.password -}}
,密码为{{ $d.password }}
{{- end -}}
,更多信息请前往控制台进行查看`
UPDATE_CONTENT_EN = `{{- $d := .resource_details -}}
Your {{ if $d.brand -}} {{ $d.brand }} {{ end -}} {{ .resource_type }} {{ $d.name }} {{ if $d.project -}} in project {{ $d.project }} {{ end -}} has been {{ .action_display }} successfully
{{- if $d.account -}}
, the acount is {{ $d.account }}
{{- end -}}
{{- if $d.password -}}
, the password is {{ $d.password }}
{{- end -}}
, and please go to the console to view more information`
)
// 用户锁定通知
const (
USER_LOCK_TITLE_CN = `{{- $d := .resource_details -}}
账号{{ $d.name }}已被锁定`
USER_LOCK_TITLE_EN = `{{- $d := .resource_details -}}
Account {{ $d.name }} has been locked`
USER_LOCK_CONTENT_CN = `{{- $d := .resource_details -}}
账号{{ $d.name }}由于异常登录已被锁定,请核实情况,如果需要为用户解锁,请到用户列表启用该用户。`
USER_LOCK_CONTENT_EN = `{{- $d := .resource_details -}}
The account {{ $d.name }} has been locked due to abnormal login. Please verify the situation. If you need to unlock the user, please go to the user list to enable the user.`
)
// 云账号状态异常通知
const (
SYNC_ACCOUNT_STATUS_TITLE_CN = `{{- $d := .resource_details -}}
云账号{{ $d.name}}状态异常`
SYNC_ACCOUNT_STATUS_TITLE_EN = `{{- $d := .resource_details -}}
The account {{ $d.name }} status is abnormal`
SYNC_ACCOUNT_STATUS_CONTENT_CN = `{{- $d := .resource_details -}}
云账号{{ $d.name }}状态异常,请及时检查。`
SYNC_ACCOUNT_STATUS_CONTENT_EN = `{{- $d := .resource_details -}}
The account {{ $d.name }} status is abnormal. Please check in time.`
)
// work阻塞通知
const (
WORK_BLOCK_TITLE_CN = `{{- $d := .resource_details -}}
服务{{ d.service_name}} worker阻塞半小时,请及时检查。`
WORK_BLOCK_TITLE_EN = `{{- $d := .resource_details -}}
The service: {{ d.service_name}} worker has been block 30 minutes.Please verify the service in time.`
WORK_BLOCK_CONTENT_CN = `{{- $d := .resource_details -}}
服务{{ d.service_name}} worker阻塞半小时,请及时检查。`
WORK_BLOCK_CONTENT_EN = `{{- $d := .resource_details -}}
The service: {{ d.service_name}} worker has been block 30 minutes.Please verify the service in time.`
)
+5 -238
View File
@@ -16,21 +16,9 @@ package models
import (
"context"
"fmt"
"html"
"html/template"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"github.com/Masterminds/sprig"
"golang.org/x/text/language"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
comapi "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/apis/notify"
api "yunion.io/x/onecloud/pkg/apis/notify"
@@ -51,21 +39,6 @@ type sEvenWebhookMsg struct {
ResourceDetails map[string]interface{} `json:"resource_details"`
}
// templateDir = "/opt/yunion/share/local-templates"
type SLocalTemplateManager struct {
templateDir string
templatesTable *sync.Map
}
var LocalTemplateManager *SLocalTemplateManager
func init() {
LocalTemplateManager = &SLocalTemplateManager{
templateDir: "/opt/yunion/share/local-templates",
templatesTable: &sync.Map{},
}
}
func languageTag(lang string) language.Tag {
var langStr string
if lang == api.TEMPLATE_LANG_CN {
@@ -77,167 +50,14 @@ func languageTag(lang string) language.Tag {
return t
}
func (lt *SLocalTemplateManager) detailsDisplay(resourceType string, details *jsonutils.JSONDict, tag language.Tag) {
fields, ok := specFields[resourceType]
if !ok {
return
}
for _, field := range fields {
if !details.Contains(field) {
continue
}
v, _ := details.GetString(field)
dv := specFieldTrans[resourceType].LookupByLang(tag, v)
details.Set(field+"_display", jsonutils.NewString(dv))
}
}
func (lt *SLocalTemplateManager) FillWithTemplate(ctx context.Context, lang string, no api.SsNotification) (params api.SendParams, err error) {
// return api.SendParams{}, nil
out, event := api.SendParams{}, no.Event
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 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 {
return api.SendParams{
Title: no.Event.StringWithDeli("_"),
Message: webhookMsg.String(),
}, nil
}
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
}
lt.detailsDisplay(rtStr, msg, tag)
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))
// get title
title, err := lt.fillWithTemplate(ctx, "title", no.ContactType, lang, event, templateParams)
if err != nil {
if errors.Cause(err) == errors.ErrNotFound {
title = no.Topic
} else {
return out, err
}
}
// get content
content, err := lt.fillWithTemplate(ctx, "content", no.ContactType, lang, event, templateParams)
if err != nil {
if errors.Cause(err) == errors.ErrNotFound {
content = no.Message
} else {
return out, err
}
}
out.Title = html.UnescapeString(title)
out.Message = html.UnescapeString(content)
return out, nil
}
var action2Topic = make(map[string]string, 0)
func specTopic(event api.SNotifyEvent) string {
switch event.Action() {
case api.ActionRebuildRoot, api.ActionChangeIpaddr, api.ActionResetPassword:
return string(api.ActionUpdate)
case api.ActionDelete:
switch event.ResourceType() {
case api.TOPIC_RESOURCE_BAREMETAL, api.TOPIC_RESOURCE_SERVER, api.TOPIC_RESOURCE_LOADBALANCER, api.TOPIC_RESOURCE_DBINSTANCE, api.TOPIC_RESOURCE_ELASTICCACHE:
return "DELETE_WITH_IP"
}
}
return ""
}
func init() {
action2Topic[string(api.ActionRebuildRoot)] = string(api.ActionUpdate)
action2Topic[string(api.ActionResetPassword)] = string(api.ActionUpdate)
action2Topic[string(api.ActionChangeIpaddr)] = string(api.ActionUpdate)
}
func (lt *SLocalTemplateManager) fillWithTemplate(ctx context.Context, titleOrContent string, contactType string, lang string, event api.SNotifyEvent, dis jsonutils.JSONObject) (string, error) {
var (
tmpl *template.Template
err error
)
actionResultStr := event.ActionWithResult("_")
topics := []string{specTopic(event), event.StringWithDeli("_"), actionResultStr}
if event.Result() == api.ResultFailed {
tempActionResourceArr := strings.Split(actionResultStr, "_")
if len(tempActionResourceArr) > 1 {
topics = append(topics, strings.Join(tempActionResourceArr[:len(tempActionResourceArr)-1], "_"))
}
}
topics = append(topics, "common")
for _, topic := range topics {
if topic == "" {
continue
}
tmpl, err = lt.getTemplate(ctx, titleOrContent, contactType, topic, lang)
if errors.Cause(err) == errors.ErrNotFound {
continue
}
if err != nil {
return "", errors.Wrap(err, "unable to getTemplate")
}
break
}
if tmpl == nil {
return "", errors.ErrNotFound
}
buf := strings.Builder{}
err = tmpl.Execute(&buf, dis.Interface())
if err != nil {
return "", errors.Wrap(err, "template.Execute")
}
return buf.String(), nil
}
var specFields = map[string][]string{
notify.TOPIC_RESOURCE_SCALINGPOLICY: {
"trigger_type",
"action",
"unit",
},
notify.TOPIC_RESOURCE_SCHEDULEDTASK: {
"resource_type",
"operation",
},
}
var specFieldTrans = map[string]i18n.Table{}
func init() {
@@ -263,68 +83,10 @@ func init() {
specFieldTrans[notify.TOPIC_RESOURCE_SCHEDULEDTASK] = stI18nTable
}
func (lt *SLocalTemplateManager) getTemplate(ctx context.Context, titleOrContent string, contactType string, topic string, lang string) (*template.Template, error) {
key := fmt.Sprintf("%s.%s@%s", topic, titleOrContent, lang)
obj, ok := lt.templatesTable.Load(key)
var elem sTemplateElem
if !ok {
// read from file
cont, err := lt.getTemplateString(ctx, titleOrContent, "", topic, lang)
if err != nil {
if err == errors.ErrNotFound {
elem = sTemplateElem{
template: nil,
}
}
return nil, err
} else {
tmp := template.New(key)
tmp.Funcs(sprig.FuncMap())
tmp, err = tmp.Parse(string(cont))
if err != nil {
return nil, err
}
elem = sTemplateElem{
template: tmp,
}
}
lt.templatesTable.Store(key, elem)
} else {
elem = obj.(sTemplateElem)
}
if elem.template == nil {
return nil, errors.ErrNotFound
}
return elem.template, nil
}
func (lt *SLocalTemplateManager) getTemplateString(ctx context.Context, titleOrContent string, contactType string, topic string, lang string) ([]byte, error) {
topic = strings.ToUpper(topic)
titleOrContent = titleOrContent + "@" + lang
var path string
if len(contactType) > 0 {
path = filepath.Join(lt.templateDir, titleOrContent, contactType, fmt.Sprintf("%s.tmpl", topic))
} else {
path = filepath.Join(lt.templateDir, titleOrContent, fmt.Sprintf("%s.tmpl", topic))
}
content, err := ioutil.ReadFile(path)
if err != nil {
if _, ok := err.(*os.PathError); ok {
return nil, errors.ErrNotFound
}
return nil, err
}
return content, nil
}
var (
notifyclientI18nTable = i18n.Table{}
)
type sTemplateElem struct {
template *template.Template
}
func setI18nTable(t i18n.Table, elems ...sI18nElme) {
for i := range elems {
t.Set(elems[i].k, i18n.NewTableEntry().EN(elems[i].en).CN(elems[i].cn))
@@ -532,6 +294,11 @@ func init() {
"cloudpods component",
"cloudpods服务组件",
},
sI18nElme{
api.TOPIC_RESOURCE_USER,
"user",
"用户",
},
sI18nElme{
string(api.ActionCreate),
"created",
+127 -15
View File
@@ -18,6 +18,8 @@ import (
"context"
"database/sql"
"fmt"
"html"
"html/template"
"strings"
"time"
@@ -69,7 +71,9 @@ type SNotification struct {
// swagger:ignore
Message string `create:"required"`
// swagger:ignore
TopicType string `json:"topic_type" width:"20" nullable:"true" update:"user" list:"user"`
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"`
@@ -279,7 +283,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
if nm.needWebconsole([]STopic{*topic}) {
// webconsole
err = nm.create(ctx, userCred, api.WEBCONSOLE, receiverIds, webconsoleContacts.UnsortedList(), input.Priority, event.GetId(), topic.Type)
err = nm.create(ctx, userCred, api.WEBCONSOLE, receiverIds, webconsoleContacts.UnsortedList(), input.Priority, event.GetId(), topic.GetId(), topic.Type)
if err != nil {
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: api.WEBCONSOLE,
@@ -292,7 +296,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
if ct == api.MOBILE {
continue
}
err := nm.create(ctx, userCred, ct, receiverIds, nil, input.Priority, event.GetId(), topic.Type)
err := nm.create(ctx, userCred, ct, receiverIds, nil, input.Priority, event.GetId(), topic.GetId(), topic.Type)
if err != nil {
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: ct,
@@ -327,7 +331,7 @@ func (nm *SNotificationManager) needWebconsole(topics []STopic) bool {
return false
}
func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds, contacts []string, priority, eventId string, topicType string) error {
func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds, contacts []string, priority, eventId, topicId string, topicType string) error {
if len(receiverIds)+len(contacts) == 0 {
return nil
}
@@ -338,6 +342,7 @@ func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.To
ReceivedAt: time.Now(),
EventId: eventId,
TopicType: topicType,
TopicId: topicId,
}
n.Id = db.DefaultUUIDGenerator()
err := nm.TableSpec().Insert(ctx, n)
@@ -534,8 +539,7 @@ func (n *SNotification) getMoreDetails(ctx context.Context, userCred mcclient.To
if err != nil {
return out, err
}
// p, err := n.TemplateStore().FillWithTemplate(ctx, lang, nn)
p, _ := n.FillWithTemplate(ctx, lang, nn)
p, err := n.GetTemplate(ctx, n.TopicId, lang, nn)
if err != nil {
return out, err
}
@@ -617,8 +621,8 @@ func (n *SNotification) AddOne() error {
return err
}
func (self *SNotificationManager) InitializeData() error {
return dataCleaning(self.TableSpec().Name())
func (nm *SNotificationManager) InitializeData() error {
return dataCleaning(nm.TableSpec().Name())
}
func dataCleaning(tableName string) error {
@@ -681,13 +685,6 @@ func (nm *SNotificationManager) ReSend(ctx context.Context, userCred mcclient.To
}
}
func (n *SNotification) FillWithTemplate(ctx context.Context, lang string, no api.SsNotification) (api.SendParams, error) {
if len(n.EventId) == 0 || n.ContactType == api.MOBILE {
return TemplateManager.FillWithTemplate(ctx, lang, no)
}
return LocalTemplateManager.FillWithTemplate(ctx, lang, no)
}
func (n *SNotification) GetNotOKReceivers() ([]SReceiver, error) {
ret := []SReceiver{}
q := ReceiverManager.Query().IsTrue("enabled")
@@ -700,3 +697,118 @@ func (n *SNotification) GetNotOKReceivers() ([]SReceiver, error) {
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)
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 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 {
return api.SendParams{
Title: no.Event.StringWithDeli("_"),
Message: webhookMsg.String(),
}, nil
}
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.Errorf("unable to parse title_cn template:%s", err.Error()))
}
stemplateContent, err = template.New("template").Parse(topic.ContentCn)
if err != nil {
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
failedReason = append(failedReason, errors.Errorf("unable to parse content_cn template:%s", err.Error()))
}
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.Errorf("unable to parse title_en template:%s", err.Error()))
}
stemplateContent, err = template.New("template").Parse(topic.ContentEn)
if err != nil {
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
failedReason = append(failedReason, errors.Errorf("unable to parse content_en template:%s", err.Error()))
}
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.Errorf("unable to parse title_en template:%s", err.Error()))
}
stemplateContent, err = template.New("template").Parse(topic.ContentEn)
if err != nil {
stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
failedReason = append(failedReason, errors.Errorf("unable to parse content_en template:%s", err.Error()))
}
}
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)
}
+53
View File
@@ -0,0 +1,53 @@
// 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 (
"time"
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
)
type SNotificationLogManager struct {
db.SLogBaseManager
}
var NotificationLogManager *SNotificationLogManager
func InitNotificationLog() {
NotificationLogManager = &SNotificationLogManager{
SLogBaseManager: db.NewLogBaseManager(SNotificationLog{}, "notification_logs_tbl", "notification", "notifications", "created_at", consts.OpsLogWithClickhouse),
}
NotificationLogManager.SetVirtualObject(NotificationLogManager)
}
// 站内信
type SNotificationLog struct {
db.SLogBase
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"`
ReceivedAt time.Time `nullable:"true" list:"user" get:"user"`
EventId string `width:"128" nullable:"true"`
SendTimes int
}
+6 -3
View File
@@ -797,13 +797,16 @@ func (r *SReceiver) PerformGetSubscription(ctx context.Context, userCred mcclien
res := []retStruct{}
for _, subscriber := range subscribers {
topicModel, err := TopicManager.FetchById(subscriber.TopicId)
if err != nil {
if errors.Cause(err) != errors.ErrNotFound {
continue
}
return nil, errors.Wrap(err, "fetch topic by id")
}
topic := topicModel.(*STopic)
if topic.Enabled == tristate.False {
continue
}
if err != nil {
return nil, errors.Wrap(err, "fetch topic by id")
}
res = append(res, retStruct{subscriber, topic.GetName()})
}
return jsonutils.Marshal(res), nil
+130 -15
View File
@@ -31,7 +31,6 @@ import (
"yunion.io/x/onecloud/pkg/apis/notify"
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/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/bitmap"
@@ -73,11 +72,16 @@ func init() {
type STopic struct {
db.SEnabledStatusStandaloneResourceBase
Type string `width:"20" nullable:"false" create:"required" update:"user" list:"user"`
Resources uint64 `nullable:"false"`
Actions uint32 `nullable:"false"`
Results tristate.TriState `default:"true"`
AdvanceDays int `nullable:"false"`
Type string `width:"20" nullable:"false" create:"required" update:"user" list:"user"`
Resources uint64 `nullable:"false"`
Actions uint32 `nullable:"false"`
Results tristate.TriState `default:"true"`
AdvanceDays int `nullable:"false"`
TitleCn string `length:"medium" nullable:"true" charset:"utf8" list:"user" update:"user" create:"optional"`
TitleEn string `length:"medium" nullable:"true" charset:"utf8" list:"user" update:"user" create:"optional"`
ContentCn string `length:"medium" nullable:"true" charset:"utf8" list:"user" update:"user" create:"optional"`
ContentEn string `length:"medium" nullable:"true" charset:"utf8" list:"user" update:"user" create:"optional"`
WebconsoleDisable tristate.TriState
}
@@ -191,6 +195,10 @@ func (sm *STopicManager) InitializeData() error {
)
t.Type = notify.TOPIC_TYPE_RESOURCE
t.Results = tristate.True
t.ContentCn = api.COMMON_CONTENT_CN
t.ContentEn = api.COMMON_CONTENT_EN
t.TitleCn = api.COMMON_TITLE_CN
t.TitleEn = api.COMMON_TITLE_EN
case DefaultResourceChangeConfig:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
@@ -200,6 +208,10 @@ func (sm *STopicManager) InitializeData() error {
t.addAction(notify.ActionChangeConfig)
t.Type = notify.TOPIC_TYPE_RESOURCE
t.Results = tristate.True
t.ContentCn = api.COMMON_CONTENT_CN
t.ContentEn = api.COMMON_CONTENT_EN
t.TitleCn = api.COMMON_TITLE_CN
t.TitleEn = api.COMMON_TITLE_EN
case DefaultResourceUpdate:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
@@ -213,6 +225,10 @@ func (sm *STopicManager) InitializeData() error {
t.addAction(notify.ActionChangeIpaddr)
t.Type = notify.TOPIC_TYPE_RESOURCE
t.Results = tristate.True
t.ContentCn = api.UPDATE_CONTENT_CN
t.ContentEn = api.UPDATE_CONTENT_EN
t.TitleCn = api.UPDATE_TITLE_CN
t.TitleEn = api.UPDATE_TITLE_EN
case DefaultResourceReleaseDue1Day:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
@@ -226,6 +242,10 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_RESOURCE
t.AdvanceDays = 1
t.Results = tristate.True
t.ContentCn = api.EXPIRED_RELEASE_CONTENT_CN
t.ContentEn = api.EXPIRED_RELEASE_CONTENT_EN
t.TitleCn = api.EXPIRED_RELEASE_TITLE_CN
t.TitleEn = api.EXPIRED_RELEASE_TITLE_EN
case DefaultResourceReleaseDue3Day:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
@@ -239,6 +259,10 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_RESOURCE
t.AdvanceDays = 3
t.Results = tristate.True
t.ContentCn = api.EXPIRED_RELEASE_CONTENT_CN
t.ContentEn = api.EXPIRED_RELEASE_CONTENT_EN
t.TitleCn = api.EXPIRED_RELEASE_TITLE_CN
t.TitleEn = api.EXPIRED_RELEASE_TITLE_EN
case DefaultResourceReleaseDue30Day:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
@@ -250,21 +274,37 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_RESOURCE
t.AdvanceDays = 30
t.Results = tristate.True
t.ContentCn = api.EXPIRED_RELEASE_CONTENT_CN
t.ContentEn = api.EXPIRED_RELEASE_CONTENT_EN
t.TitleCn = api.EXPIRED_RELEASE_TITLE_CN
t.TitleEn = api.EXPIRED_RELEASE_TITLE_EN
case DefaultScheduledTaskExecute:
t.addResources(notify.TOPIC_RESOURCE_SCHEDULEDTASK)
t.addAction(notify.ActionExecute)
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
t.Results = tristate.True
t.ContentCn = api.SCHEDULEDTASK_EXECUTE_CONTENT_CN
t.ContentEn = api.SCHEDULEDTASK_EXECUTE_CONTENT_EN
t.TitleCn = api.SCHEDULEDTASK_EXECUTE_TITLE_CN
t.TitleEn = api.SCHEDULEDTASK_EXECUTE_TITLE_EN
case DefaultScalingPolicyExecute:
t.addResources(notify.TOPIC_RESOURCE_SCALINGPOLICY)
t.addAction(notify.ActionExecute)
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
t.Results = tristate.True
t.ContentCn = api.SCALINGPOLICY_EXECUTE_CONTENT_CN
t.ContentEn = api.SCALINGPOLICY_EXECUTE_CONTENT_EN
t.TitleCn = api.SCALINGPOLICY_EXECUTE_TITLE_CN
t.TitleEn = api.SCALINGPOLICY_EXECUTE_TITLE_EN
case DefaultSnapshotPolicyExecute:
t.addResources(notify.TOPIC_RESOURCE_SNAPSHOTPOLICY)
t.addAction(notify.ActionExecute)
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
t.Results = tristate.True
t.ContentCn = api.SNAPSHOTPOLICY_EXECUTE_CONTENT_CN
t.ContentEn = api.SNAPSHOTPOLICY_EXECUTE_CONTENT_EN
t.TitleCn = api.SNAPSHOTPOLICY_EXECUTE_TITLE_CN
t.TitleEn = api.SNAPSHOTPOLICY_EXECUTE_TITLE_EN
case DefaultResourceOperationFailed:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
@@ -317,6 +357,10 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_RESOURCE
t.WebconsoleDisable = tristate.True
t.Results = tristate.True
t.ContentCn = api.COMMON_CONTENT_CN
t.ContentEn = api.COMMON_CONTENT_EN
t.TitleCn = api.COMMON_TITLE_CN
t.TitleEn = api.COMMON_TITLE_EN
case DefaultSystemExceptionEvent:
t.addResources(
notify.TOPIC_RESOURCE_HOST,
@@ -329,6 +373,10 @@ func (sm *STopicManager) InitializeData() error {
)
t.Type = notify.TOPIC_TYPE_RESOURCE
t.Results = tristate.False
t.ContentCn = api.EXCEPTION_CONTENT_CN
t.ContentEn = api.EXCEPTION_CONTENT_EN
t.TitleCn = api.EXCEPTION_TITLE_CN
t.TitleEn = api.EXCEPTION_TITLE_EN
case DefaultChecksumTestFailed:
t.addResources(
notify.TOPIC_RESOURCE_DB_TABLE_RECORD,
@@ -342,6 +390,10 @@ func (sm *STopicManager) InitializeData() error {
)
t.Type = notify.TOPIC_TYPE_SECURITY
t.Results = tristate.False
t.ContentCn = api.CHECKSUM_TEST_FAILED_CONTENT_CN
t.ContentEn = api.CHECKSUM_TEST_FAILED_CONTENT_EN
t.TitleCn = api.CHECKSUM_TEST_FAILED_TITLE_CN
t.TitleEn = api.CHECKSUM_TEST_FAILED_TITLE_EN
case DefaultUserLock:
t.addResources(
notify.TOPIC_RESOURCE_USER,
@@ -351,6 +403,10 @@ func (sm *STopicManager) InitializeData() error {
)
t.Type = notify.TOPIC_TYPE_SECURITY
t.Results = tristate.True
t.ContentCn = api.USER_LOCK_CONTENT_CN
t.ContentEn = api.USER_LOCK_CONTENT_EN
t.TitleCn = api.USER_LOCK_TITLE_CN
t.TitleEn = api.USER_LOCK_TITLE_EN
case DefaultActionLogExceedCount:
t.addResources(
notify.TOPIC_RESOURCE_ACTION_LOG,
@@ -360,6 +416,10 @@ func (sm *STopicManager) InitializeData() error {
)
t.Type = notify.TOPIC_TYPE_RESOURCE
t.Results = tristate.True
t.ContentCn = api.ACTION_LOG_EXCEED_COUNT_CONTENT_CN
t.ContentEn = api.ACTION_LOG_EXCEED_COUNT_CONTENT_EN
t.TitleCn = api.ACTION_LOG_EXCEED_COUNT_TITLE_CN
t.TitleEn = api.ACTION_LOG_EXCEED_COUNT_TITLE_EN
case DefaultSyncAccountStatus:
t.addResources(
notify.TOPIC_RESOURCE_ACCOUNT_STATUS,
@@ -369,6 +429,10 @@ func (sm *STopicManager) InitializeData() error {
)
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
t.Results = tristate.True
t.ContentCn = api.SYNC_ACCOUNT_STATUS_CONTENT_CN
t.ContentEn = api.SYNC_ACCOUNT_STATUS_CONTENT_EN
t.TitleCn = api.SYNC_ACCOUNT_STATUS_TITLE_CN
t.TitleEn = api.SYNC_ACCOUNT_STATUS_TITLE_EN
case DefaultPasswordExpireDue1Day:
t.addResources(
notify.TOPIC_RESOURCE_USER,
@@ -379,6 +443,10 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_SECURITY
t.AdvanceDays = 1
t.Results = tristate.True
t.ContentCn = api.PWD_EXPIRE_SOON_CONTENT_CN
t.ContentEn = api.PWD_EXPIRE_SOON_CONTENT_EN
t.TitleCn = api.PWD_EXPIRE_SOON_TITLE_CN
t.TitleEn = api.PWD_EXPIRE_SOON_TITLE_EN
case DefaultPasswordExpireDue7Day:
t.addResources(
notify.TOPIC_RESOURCE_USER,
@@ -389,6 +457,10 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_SECURITY
t.AdvanceDays = 7
t.Results = tristate.True
t.ContentCn = api.PWD_EXPIRE_SOON_CONTENT_CN
t.ContentEn = api.PWD_EXPIRE_SOON_CONTENT_EN
t.TitleCn = api.PWD_EXPIRE_SOON_TITLE_CN
t.TitleEn = api.PWD_EXPIRE_SOON_TITLE_EN
case DefaultNetOutOfSync:
t.addResources(
notify.TOPIC_RESOURCE_NET,
@@ -399,6 +471,10 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
t.AdvanceDays = 0
t.Results = tristate.True
t.ContentCn = api.NET_OUT_OF_SYNC_CONTENT_CN
t.ContentEn = api.NET_OUT_OF_SYNC_CONTENT_EN
t.TitleCn = api.NET_OUT_OF_SYNC_TITLE_CN
t.TitleEn = api.NET_OUT_OF_SYNC_TITLE_EN
case DefaultMysqlOutOfSync:
t.addResources(
notify.TOPIC_RESOURCE_DBINSTANCE,
@@ -409,6 +485,10 @@ func (sm *STopicManager) InitializeData() error {
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
t.AdvanceDays = 0
t.Results = tristate.True
t.ContentCn = api.MYSQL_OUT_OF_SYNC_CONTENT_CN
t.ContentEn = api.MYSQL_OUT_OF_SYNC_CONTENT_EN
t.TitleCn = api.MYSQL_OUT_OF_SYNC_TITLE_CN
t.TitleEn = api.MYSQL_OUT_OF_SYNC_TITLE_EN
case DefaultServiceAbnormal:
t.addResources(
notify.TOPIC_RESOURCE_SERVICE,
@@ -418,6 +498,10 @@ func (sm *STopicManager) InitializeData() error {
)
t.Results = tristate.True
t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS
t.ContentCn = api.SERVICE_ABNORMAL_CONTENT_CN
t.ContentEn = api.SERVICE_ABNORMAL_CONTENT_EN
t.TitleCn = api.SERVICE_ABNORMAL_TITLE_CN
t.TitleEn = api.SERVICE_ABNORMAL_TITLE_EN
case DefaultServerPanicked:
t.addResources(
notify.TOPIC_RESOURCE_SERVER,
@@ -427,9 +511,25 @@ func (sm *STopicManager) InitializeData() error {
)
t.Results = tristate.False
t.Type = notify.TOPIC_TYPE_RESOURCE
t.ContentCn = api.SERVER_PANICKED_CONTENT_CN
t.ContentEn = api.SERVER_PANICKED_CONTENT_EN
t.TitleCn = api.SERVER_PANICKED_TITLE_CN
t.TitleEn = api.SERVER_PANICKED_TITLE_EN
}
if topic == nil {
if len(t.ContentCn) == 0 {
t.ContentCn = api.COMMON_CONTENT_CN
}
if len(t.ContentEn) == 0 {
t.ContentEn = api.COMMON_CONTENT_EN
}
if len(t.TitleCn) == 0 {
t.TitleCn = api.COMMON_TITLE_CN
}
if len(t.TitleEn) == 0 {
t.TitleEn = api.COMMON_TITLE_EN
}
err := sm.TableSpec().Insert(ctx, t)
if err != nil {
return errors.Wrapf(err, "unable to insert %s", name)
@@ -441,6 +541,30 @@ func (sm *STopicManager) InitializeData() error {
topic.Type = t.Type
topic.Results = t.Results
topic.WebconsoleDisable = t.WebconsoleDisable
if len(topic.ContentCn) == 0 {
if len(t.ContentCn) == 0 {
t.ContentCn = api.COMMON_CONTENT_CN
}
topic.ContentCn = t.ContentCn
}
if len(topic.ContentEn) == 0 {
if len(t.ContentEn) == 0 {
t.ContentEn = api.COMMON_CONTENT_EN
}
topic.ContentEn = t.ContentEn
}
if len(topic.TitleCn) == 0 {
if len(t.TitleCn) == 0 {
t.TitleCn = api.COMMON_TITLE_CN
}
topic.TitleCn = t.TitleCn
}
if len(topic.TitleEn) == 0 {
if len(t.TitleEn) == 0 {
t.TitleEn = api.COMMON_TITLE_EN
}
topic.TitleEn = t.TitleEn
}
return nil
})
if err != nil {
@@ -799,15 +923,6 @@ func (rc *sConverter) action(actionValue int) notify.SAction {
return a.(notify.SAction)
}
func (self *STopic) StartMessageSendTask(ctx context.Context, userCred mcclient.TokenCredential, input api.NotificationManagerEventNotifyInput) error {
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
task, err := taskman.TaskManager.NewTask(ctx, "TopicMessageSendTask", self, userCred, params, "", "")
if err != nil {
return errors.Wrapf(err, "NewTask")
}
return task.ScheduleRun(nil)
}
func (self *STopic) CreateEvent(ctx context.Context, resType, action, message string) (*SEvent, error) {
eve := &SEvent{
Message: message,
+1 -1
View File
@@ -202,7 +202,7 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone
continue
}
// send
p, err := notification.FillWithTemplate(ctx, lang, nn)
p, err := notification.GetTemplate(ctx, event.TopicId, lang, nn)
if err != nil {
logclient.AddSimpleActionLog(notification, logclient.ACT_SEND_NOTIFICATION, errors.Wrapf(err, "FillWithTemplate(%s)", lang), self.GetUserCred(), false)
continue
-302
View File
@@ -1,302 +0,0 @@
// 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 tasks
import (
"context"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/utils"
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/mcclient/auth"
identity "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
"yunion.io/x/onecloud/pkg/notify/models"
"yunion.io/x/onecloud/pkg/notify/options"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type TopicMessageSendTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(TopicMessageSendTask{})
}
func (topicMessageSendTask *TopicMessageSendTask) taskFailed(ctx context.Context, topic *models.STopic, err error) {
logclient.AddActionLogWithContext(ctx, topic, logclient.ACT_SEND_NOTIFICATION, err, topicMessageSendTask.UserCred, false)
topicMessageSendTask.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (topicMessageSendTask *TopicMessageSendTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
failedReasons := []string{}
topic := obj.(*models.STopic)
input := api.NotificationManagerEventNotifyInput{}
topicMessageSendTask.GetParams().Unmarshal(&input)
message := jsonutils.Marshal(input.ResourceDetails).String()
sevent := api.Event.WithAction(input.Action).WithResourceType(input.ResourceType)
event, err := models.EventManager.CreateEvent(ctx, sevent.String(), topic.Id, message, string(input.Action), string(input.ResourceType), input.AdvanceDays)
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrap(err, "unable to create Event"))
return
}
snotification := api.SsNotification{
Topic: topic.Id,
Message: event.Message,
Event: sevent,
AdvanceDays: event.AdvanceDays,
}
n := models.SNotification{
Priority: input.Priority,
EventId: event.GetId(),
TopicType: topic.Type,
}
n.Id = db.DefaultUUIDGenerator()
for _, contact := range input.ContactTypes {
n.ContactType = contact
err = models.NotificationManager.TableSpec().Insert(ctx, &n)
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrap(err, "notifications insert"))
return
}
}
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrap(err, "unable to fetch receivers by ids"))
return
}
needWebconsole := false
// 本地模板
send, _ := models.LocalTemplateManager.FillWithTemplate(ctx, api.TEMPLATE_LANG_CN, snotification)
// 远程模板(短信)
remoteSend, _ := models.TemplateManager.FillWithTemplate(ctx, api.TEMPLATE_LANG_CN, snotification)
send.Event = event.Event
remoteSend.Event = event.Event
websocketDriver := models.GetDriver(api.WEBSOCKET)
err = websocketDriver.Send(send)
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrapf(err, "websocket send"))
}
scribers, err := topic.GetEnabledSubscribers(input.ProjectDomainId, input.ProjectId)
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrapf(err, "GetSubscribers"))
return
}
robots := map[string]*models.SRobot{}
userIds := []string{}
for i := range scribers {
switch scribers[i].Type {
case api.SUBSCRIBER_TYPE_RECEIVER:
recvs, err := scribers[i].GetEnabledReceivers()
if err != nil {
log.Errorf("scribers[%d].GetEnabledReceivers err :%s", i, err)
return
}
// ids := []string{}
err = sendByReceivers(recvs, []string{}, n, send, remoteSend, needWebconsole)
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrap(err, "sendByReceivers"))
return
}
case api.SUBSCRIBER_TYPE_ROLE:
query := jsonutils.NewDict()
query.Set("roles", jsonutils.NewStringArray([]string{scribers[i].Identification}))
query.Set("effective", jsonutils.JSONTrue)
if scribers[i].RoleScope == api.SUBSCRIBER_SCOPE_DOMAIN {
query.Set("project_domain_id", jsonutils.NewString(scribers[i].ResourceAttributionId))
} else if scribers[i].RoleScope == api.SUBSCRIBER_SCOPE_PROJECT {
query.Add(jsonutils.NewString(scribers[i].ResourceAttributionId), "scope", "project", "id")
}
s := auth.GetAdminSession(ctx, options.Options.Region)
ret, err := identity.RoleAssignments.List(s, query)
if err != nil {
logclient.AddActionLogWithContext(ctx, topic, logclient.ACT_SEND_NOTIFICATION, errors.Wrapf(err, "RoleAssignments.List"), topicMessageSendTask.UserCred, false)
continue
}
users := []struct {
User struct {
Id string
}
}{}
jsonutils.Update(&users, ret.Data)
for _, user := range users {
userIds = append(userIds, user.User.Id)
}
recvs, err := models.ReceiverManager.FetchEnableReceiversByIdOrNames(ctx, userIds...)
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrap(err, "FetchEnableReceiversByIdOrNames"))
return
}
err = sendByReceivers(recvs, []string{}, n, send, remoteSend, false)
if err != nil {
topicMessageSendTask.taskFailed(ctx, topic, errors.Wrap(err, "sendByReceivers"))
return
}
case api.SUBSCRIBER_TYPE_ROBOT:
robot, err := scribers[i].GetRobot()
if err != nil {
logclient.AddActionLogWithContext(ctx, topic, logclient.ACT_SEND_NOTIFICATION, errors.Wrapf(err, "GetRobot"), topicMessageSendTask.UserCred, false)
continue
}
if !robot.Enabled.Bool() {
continue
}
robots[robot.Id] = robot
robotType := ""
switch robot.Type {
case api.FEISHU:
robotType = api.FEISHU_ROBOT
case api.DINGTALK:
robotType = api.DINGTALK_ROBOT
case api.WORKWX:
robotType = api.WORKWX_ROBOT
case api.WEBHOOK:
robotType = api.WEBHOOK_ROBOT
}
n.ContactType = robotType
err = models.NotificationManager.TableSpec().Insert(ctx, &n)
if err != nil {
failedReasons = append(failedReasons, err.Error())
}
session := auth.GetAdminSession(ctx, options.Options.Region)
rn := models.SReceiverNotification{
NotificationID: n.Id,
ReceiverType: api.RECEIVER_TYPE_ROBOT,
ReceiverID: robot.Id,
Status: api.RECEIVER_NOTIFICATION_RECEIVED,
SendBy: session.GetUserId(),
}
models.ReceiverNotificationManager.TableSpec().Insert(ctx, &rn)
driver := models.GetDriver(robotType)
if driver == nil {
log.Errorln(robotType)
}
send.Receivers = api.SNotifyReceiver{Contact: robot.Address}
err = driver.Send(send)
if err != nil {
log.Errorln("this is err:", err)
}
}
}
if len(failedReasons) > 0 {
reason := strings.Join(failedReasons, "; ")
topicMessageSendTask.taskFailed(ctx, topic, errors.Error(reason))
return
}
logclient.AddActionLogWithContext(ctx, topic, logclient.ACT_SEND_NOTIFICATION, jsonutils.Marshal(input), topicMessageSendTask.UserCred, true)
topicMessageSendTask.SetStageComplete(ctx, nil)
}
func sendByReceivers(recvs []models.SReceiver, receiverIds []string, n models.SNotification, send, remoteSend api.SendParams, needWebconsole bool) error {
ctx := context.Background()
failedReasons := []string{}
ids := []string{}
idFailedMap := make(map[string][]string)
for i, recv := range recvs {
// 检查是否发送email
if recvs[i].EnabledEmail == tristate.True {
if recvs[i].VerifiedEmail == tristate.True {
if recvs[i].EnabledEmail == tristate.True {
driver := models.GetDriver(api.EMAIL)
send.Receivers.Contact = recvs[i].Email
err := driver.Send(send)
if err != nil {
failedReasons = append(failedReasons, errors.Wrapf(err, "email send").Error())
}
}
} else {
log.Errorln(errors.Errorf("email has no verified: %s,receiver name: %s", recvs[i].Email, recvs[i].Name))
}
}
// 检查是否发送短信
if recvs[i].EnabledMobile == tristate.True {
if recvs[i].VerifiedMobile == tristate.True {
if recvs[i].EnabledMobile == tristate.True {
driver := models.GetDriver(api.MOBILE)
send.Receivers.Contact = recvs[i].Mobile
err := driver.Send(remoteSend)
if err != nil {
failedReasons = append(failedReasons, errors.Wrapf(err, "mobile send").Error())
}
}
} else {
log.Errorln(errors.Errorf("sms has no verified: %s,receiver name: %s", recvs[i].Mobile, recvs[i].Name))
}
}
idFailedMap[recv.Id] = append(idFailedMap[recv.Id], failedReasons...)
ids = append(ids, recvs[i].Id)
}
session := auth.GetAdminSession(ctx, options.Options.Region)
for _, receiverId := range receiverIds {
rn := models.SReceiverNotification{
NotificationID: n.Id,
Status: api.RECEIVER_NOTIFICATION_RECEIVED,
SendBy: session.GetUserId(),
}
if utils.IsInStringArray(receiverId, ids) {
rn.ReceiverType = api.RECEIVER_TYPE_USER
rn.ReceiverID = receiverId
} else {
rn.ReceiverType = api.RECEIVER_TYPE_CONTACT
rn.Contact = receiverId
}
models.ReceiverNotificationManager.TableSpec().Insert(ctx, &rn)
}
rm := &models.SReceiverManager{}
// 从subcontacts表中获取数据并发送
subContactsMap, err := rm.FetchSubContacts(ids)
if err != nil {
return errors.Wrap(err, "rm.FetchSubContacts")
}
for id, subContacts := range subContactsMap {
for _, subContact := range subContacts {
n.Topic = send.Topic
n.Status = api.NOTIFICATION_STATUS_SENDING
if subContact.Enabled == tristate.False {
continue
}
n.ContactType = subContact.Type
models.NotificationManager.TableSpec().Insert(context.Background(), &n)
driver := models.GetDriver(subContact.Type)
send.Receivers = api.SNotifyReceiver{
Contact: subContact.Contact,
}
err = driver.Send(send)
if err != nil {
failedReasons = append(failedReasons, errors.Wrapf(err, "content type:%s,receiver:%s", subContact.Type, subContact.ReceiverID).Error())
}
}
if len(idFailedMap[id]) == 0 {
}
}
return nil
}
func createReceiverNotification(receiverIds []string, recvs []models.SReceiver, n models.SNotification) {
}