Merge branch 'master' into dev_ui

This commit is contained in:
wang-liang0615 2023-09-26 20:34:14 +08:00 committed by GitHub
commit 2b1b0333ff
6 changed files with 251 additions and 47 deletions

View File

@ -230,3 +230,59 @@ def init_department():
cli.init_wide_company() cli.init_wide_company()
cli.create_acl_role_with_department() cli.create_acl_role_with_department()
cli.init_backend_resource() cli.init_backend_resource()
@click.command()
@with_appcontext
def common_check_new_columns():
"""
add new columns to tables
"""
from api.extensions import db
from sqlalchemy import inspect, text
def get_model_by_table_name(table_name):
for model in db.Model.registry._class_registry.values():
if hasattr(model, '__tablename__') and model.__tablename__ == table_name:
return model
return None
def add_new_column(table_name, new_column):
column_type = new_column.type.compile(engine.dialect)
default_value = new_column.default.arg if new_column.default else None
sql = f"ALTER TABLE {table_name} ADD COLUMN {new_column.name} {column_type} "
if new_column.comment:
sql += f" comment '{new_column.comment}'"
if column_type == 'JSON':
pass
elif default_value:
if column_type.startswith('VAR') or column_type.startswith('Text'):
if default_value is None or len(default_value) == 0:
pass
else:
sql += f" DEFAULT {default_value}"
sql = text(sql)
db.session.execute(sql)
engine = db.get_engine()
inspector = inspect(engine)
table_names = inspector.get_table_names()
for table_name in table_names:
existed_columns = inspector.get_columns(table_name)
existed_column_name_list = [c['name'] for c in existed_columns]
model = get_model_by_table_name(table_name)
if model is None:
continue
model_columns = model.__table__.columns._all_columns
for column in model_columns:
if column.name not in existed_column_name_list:
try:
add_new_column(table_name, column)
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
except Exception as e:
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
current_app.logger.error(e)

View File

@ -0,0 +1,94 @@
from api.models.common_setting import NoticeConfig
from wtforms import Form
from wtforms import StringField
from wtforms import validators
from flask import abort
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
class NoticeConfigCRUD(object):
@staticmethod
def add_notice_config(**kwargs):
NoticeConfigCRUD.check_platform(kwargs.get('platform'))
try:
return NoticeConfig.create(
**kwargs
)
except Exception as e:
return abort(400, str(e))
@staticmethod
def check_platform(platform):
NoticeConfig.get_by(first=True, to_dict=False, platform=platform) and abort(400, f"{platform} 已存在!")
@staticmethod
def edit_notice_config(_id, **kwargs):
existed = NoticeConfigCRUD.get_notice_config_by_id(_id)
try:
return existed.update(**kwargs)
except Exception as e:
return abort(400, str(e))
@staticmethod
def get_notice_config_by_id(_id):
return NoticeConfig.get_by(first=True, to_dict=False, id=_id) or abort(400, f"{_id} 配置项不存在!")
@staticmethod
def get_all():
return NoticeConfig.get_by(to_dict=True)
@staticmethod
def test_send_email(receive_address, **kwargs):
# 设置发送方和接收方的电子邮件地址
sender_email = 'test@test.com'
sender_name = 'Test Sender'
recipient_email = receive_address
recipient_name = receive_address
subject = 'Test Email'
body = 'This is a test email'
message = MIMEText(body, 'plain', 'utf-8')
message['From'] = formataddr((sender_name, sender_email))
message['To'] = formataddr((recipient_name, recipient_email))
message['Subject'] = subject
smtp_server = kwargs.get('server')
smtp_port = kwargs.get('port')
smtp_username = kwargs.get('username')
smtp_password = kwargs.get('password')
if kwargs.get('mail_type') == 'SMTP':
smtp_connection = smtplib.SMTP(smtp_server, smtp_port)
else:
smtp_connection = smtplib.SMTP_SSL(smtp_server, smtp_port)
if kwargs.get('is_login'):
smtp_connection.login(smtp_username, smtp_password)
smtp_connection.sendmail(sender_email, recipient_email, message.as_string())
smtp_connection.quit()
return 1
class NoticeConfigForm(Form):
platform = StringField(validators=[
validators.DataRequired(message="平台 不能为空"),
validators.Length(max=255),
])
info = StringField(validators=[
validators.DataRequired(message="信息 不能为空"),
validators.Length(max=255),
])
class NoticeConfigUpdateForm(Form):
info = StringField(validators=[
validators.DataRequired(message="信息 不能为空"),
validators.Length(max=255),
])

View File

@ -53,5 +53,6 @@ class ErrFormat(CommonErrFormat):
username_is_required = "username不能为空" username_is_required = "username不能为空"
email_is_required = "邮箱不能为空" email_is_required = "邮箱不能为空"
email_format_error = "邮箱格式错误" email_format_error = "邮箱格式错误"
email_send_timeout = "邮件发送超时"
common_data_not_found = "ID {} 找不到记录" common_data_not_found = "ID {} 找不到记录"

View File

@ -47,6 +47,8 @@ class Employee(ModelWithoutPK):
last_login = db.Column(db.TIMESTAMP, nullable=True) last_login = db.Column(db.TIMESTAMP, nullable=True)
block = db.Column(db.Integer, default=0) block = db.Column(db.Integer, default=0)
notice_info = db.Column(db.JSON, default={})
_department = db.relationship( _department = db.relationship(
'Department', backref='common_employee.department_id', 'Department', backref='common_employee.department_id',
lazy='joined' lazy='joined'
@ -87,3 +89,10 @@ class CommonData(Model):
data_type = db.Column(db.VARCHAR(255), default='') data_type = db.Column(db.VARCHAR(255), default='')
data = db.Column(db.JSON) data = db.Column(db.JSON)
class NoticeConfig(Model):
__tablename__ = "common_notice_config"
platform = db.Column(db.VARCHAR(255), nullable=False)
info = db.Column(db.JSON)

View File

@ -4,8 +4,6 @@
import json import json
import time import time
import jinja2
import requests
from flask import current_app from flask import current_app
from flask_login import login_user from flask_login import login_user
@ -18,7 +16,6 @@ from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.mail import send_mail
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.utils import Lock from api.lib.utils import Lock
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
@ -28,7 +25,9 @@ from api.models.cmdb import CITypeAttribute
@celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE) @celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE)
def ci_cache(ci_id): def ci_cache(ci_id, operate_type, record_id):
from api.lib.cmdb.ci import CITriggerManager
time.sleep(0.01) time.sleep(0.01)
db.session.remove() db.session.remove()
@ -42,9 +41,14 @@ def ci_cache(ci_id):
current_app.logger.info("{0} flush..........".format(ci_id)) current_app.logger.info("{0} flush..........".format(ci_id))
current_app.test_request_context().push()
login_user(UserCache.get('worker'))
CITriggerManager.fire(operate_type, ci_dict, record_id)
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE) @celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
def batch_ci_cache(ci_ids): def batch_ci_cache(ci_ids, ): # only for attribute change index
time.sleep(1) time.sleep(1)
db.session.remove() db.session.remove()
@ -61,7 +65,10 @@ def batch_ci_cache(ci_ids):
@celery.task(name="cmdb.ci_delete", queue=CMDB_QUEUE) @celery.task(name="cmdb.ci_delete", queue=CMDB_QUEUE)
def ci_delete(ci_id): def ci_delete(ci_dict, operate_type, record_id):
from api.lib.cmdb.ci import CITriggerManager
ci_id = ci_dict.get('_id')
current_app.logger.info(ci_id) current_app.logger.info(ci_id)
if current_app.config.get("USE_ES"): if current_app.config.get("USE_ES"):
@ -71,6 +78,11 @@ def ci_delete(ci_id):
current_app.logger.info("{0} delete..........".format(ci_id)) current_app.logger.info("{0} delete..........".format(ci_id))
current_app.test_request_context().push()
login_user(UserCache.get('worker'))
CITriggerManager.fire(operate_type, ci_dict, record_id)
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE) @celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
def ci_relation_cache(parent_id, child_id): def ci_relation_cache(parent_id, child_id):
@ -168,46 +180,6 @@ def ci_type_attribute_order_rebuild(type_id):
order += 1 order += 1
@celery.task(name='cmdb.trigger_notify', queue=CMDB_QUEUE)
def trigger_notify(notify, ci_id):
from api.lib.perm.acl.cache import UserCache
def _wrap_mail(mail_to):
if "@" not in mail_to:
user = UserCache.get(mail_to)
if user:
return user.email
return mail_to
db.session.remove()
m = api.lib.cmdb.ci.CIManager()
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
subject = jinja2.Template(notify.get('subject') or "").render(ci_dict)
body = jinja2.Template(notify.get('body') or "").render(ci_dict)
if notify.get('wx_to'):
to_user = jinja2.Template('|'.join(notify['wx_to'])).render(ci_dict)
url = current_app.config.get("WX_URI")
data = {"to_user": to_user, "content": subject}
try:
requests.post(url, data=data)
except Exception as e:
current_app.logger.error(str(e))
if notify.get('mail_to'):
try:
if len(subject) > 700:
subject = subject[:600] + "..." + subject[-100:]
send_mail("", [_wrap_mail(jinja2.Template(i).render(ci_dict))
for i in notify['mail_to'] if i], subject, body)
except Exception as e:
current_app.logger.error("Send mail failed: {0}".format(str(e)))
@celery.task(name="cmdb.calc_computed_attribute", queue=CMDB_QUEUE) @celery.task(name="cmdb.calc_computed_attribute", queue=CMDB_QUEUE)
def calc_computed_attribute(attr_id, uid): def calc_computed_attribute(attr_id, uid):
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
@ -217,7 +189,8 @@ def calc_computed_attribute(attr_id, uid):
current_app.test_request_context().push() current_app.test_request_context().push()
login_user(UserCache.get(uid)) login_user(UserCache.get(uid))
cim = CIManager()
for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False): for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False):
cis = CI.get_by(type_id=i.type_id, to_dict=False) cis = CI.get_by(type_id=i.type_id, to_dict=False)
for ci in cis: for ci in cis:
CIManager.update(ci.id, {}) cim.update(ci.id, {})

View File

@ -0,0 +1,71 @@
from flask import request, abort, current_app
from werkzeug.datastructures import MultiDict
from api.lib.perm.auth import auth_with_app_token
from api.models.common_setting import NoticeConfig
from api.resource import APIView
from api.lib.common_setting.notice_config import NoticeConfigForm, NoticeConfigUpdateForm, NoticeConfigCRUD
from api.lib.decorator import args_required
from api.lib.common_setting.resp_format import ErrFormat
prefix = '/notice_config'
class NoticeConfigView(APIView):
url_prefix = (f'{prefix}',)
@args_required('platform')
@auth_with_app_token
def get(self):
platform = request.args.get('platform')
res = NoticeConfig.get_by(first=True, to_dict=True, platform=platform) or {}
return self.jsonify(res)
def post(self):
form = NoticeConfigForm(MultiDict(request.json))
if not form.validate():
abort(400, ','.join(['{}: {}'.format(filed, ','.join(msg)) for filed, msg in form.errors.items()]))
data = NoticeConfigCRUD.add_notice_config(**form.data)
return self.jsonify(data.to_dict())
class NoticeConfigUpdateView(APIView):
url_prefix = (f'{prefix}/<int:_id>',)
def put(self, _id):
form = NoticeConfigUpdateForm(MultiDict(request.json))
if not form.validate():
abort(400, ','.join(['{}: {}'.format(filed, ','.join(msg)) for filed, msg in form.errors.items()]))
data = NoticeConfigCRUD.edit_notice_config(_id, **form.data)
return self.jsonify(data.to_dict())
class CheckEmailServer(APIView):
url_prefix = (f'{prefix}/send_test_email',)
def post(self):
receive_address = request.args.get('receive_address')
info = request.values.get('info')
try:
result = NoticeConfigCRUD.test_send_email(receive_address, **info)
return self.jsonify(result=result)
except Exception as e:
current_app.logger.error('test_send_email err:')
current_app.logger.error(e)
if 'Timed Out' in str(e):
abort(400, ErrFormat.email_send_timeout)
abort(400, f"{str(e)}")
class NoticeConfigGetView(APIView):
method_decorators = []
url_prefix = (f'{prefix}/all',)
@auth_with_app_token
def get(self):
res = NoticeConfigCRUD.get_all()
return self.jsonify(res)