mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-29 03:51:54 +08:00
fix(notify): reset notifications
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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.`
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user