Files
2026-07-13 12:31:34 +08:00

739 lines
19 KiB
Python

# -*- coding: utf-8 -*-
"""
由于历史原因,domain指代 SSL证书的域名
"""
from __future__ import print_function, unicode_literals, absolute_import, division
from operator import itemgetter
from flask import request, g
from playhouse.shortcuts import model_to_dict, fn
from domain_admin.enums.operation_enum import OperationEnum
from domain_admin.enums.role_enum import RoleEnum
from domain_admin.enums.ssl_type_enum import SSLTypeEnum
from domain_admin.model.address_model import AddressModel
from domain_admin.model.domain_info_model import DomainInfoModel
from domain_admin.model.domain_model import DomainModel
from domain_admin.model.group_model import GroupModel
from domain_admin.model.group_user_model import GroupUserModel
from domain_admin.service import async_task_service, domain_info_service, group_service, operation_service, auth_service
from domain_admin.service import domain_service
from domain_admin.service import file_service
from domain_admin.utils import datetime_util, domain_util
from domain_admin.utils.cert_util import cert_consts
from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException, ForbiddenAppException
@auth_service.permission(role=RoleEnum.USER)
@operation_service.operation_log_decorator(
model=DomainModel,
operation_type_id=OperationEnum.CREATE
)
def add_domain():
"""
添加域名
:return:
"""
current_user_id = g.user_id
domain = request.json['domain']
alias = request.json.get('alias') or ''
group_id = request.json.get('group_id') or 0
# is_dynamic_host = request.json.get('is_dynamic_host', False)
start_time = request.json.get('start_time')
expire_time = request.json.get('expire_time')
auto_update = request.json.get('auto_update', True)
port = request.json.get('port') or cert_consts.SSL_DEFAULT_PORT
ssl_type = request.json.get('ssl_type', SSLTypeEnum.SSL_TLS)
data = {
# 基本信息
'user_id': current_user_id,
'domain': domain.strip(),
'port': int(port), # fix: TypeError: an integer is required (got type str)
'root_domain': domain_util.get_root_domain(domain),
'alias': alias,
'group_id': group_id,
# 'is_dynamic_host': is_dynamic_host,
'start_time': start_time,
'expire_time': expire_time,
'auto_update': auto_update,
'ssl_type': ssl_type,
}
row = DomainModel.create(**data)
if auto_update:
domain_service.update_domain_row(row)
# 顺带添加到域名监测列表
if not domain_util.is_ipv4(domain):
first_domain_info_row = DomainInfoModel.select(
DomainInfoModel.id
).where(
DomainInfoModel.domain == data['root_domain'],
DomainInfoModel.user_id == current_user_id
).first()
if not first_domain_info_row:
domain_info_service.add_domain_info(
domain=domain_util.get_root_domain(domain),
comment=alias,
group_id=group_id,
user_id=current_user_id,
)
return {'id': row.id}
@auth_service.permission(role=RoleEnum.USER)
@operation_service.operation_log_decorator(
model=DomainModel,
operation_type_id=OperationEnum.UPDATE
)
def update_domain_by_id():
"""
更新数据
id domain alias group_id notify_status
:return:
"""
current_user_id = g.user_id
data = request.json
domain_id = request.json['id']
# domain_service.check_permission_and_get_row(domain_id, current_user_id)
data['update_time'] = datetime_util.get_datetime()
data['group_id'] = data.get('group_id') or 0
# data check
before_domain_row = DomainModel.select().where(
DomainModel.id == domain_id
).first()
if not before_domain_row:
raise DataNotFoundAppException()
# edit permission check
has_permission = domain_service.has_edit_permission(
domain_row=before_domain_row,
user_id=current_user_id
)
if not has_permission:
raise ForbiddenAppException()
DomainModel.update(data).where(
DomainModel.id == domain_id
).execute()
after_domain_row = DomainModel.get_by_id(domain_id)
# 域名、端口、SSL加密方式没改变,就不更新
if before_domain_row.domain == after_domain_row.domain \
and before_domain_row.port == after_domain_row.port \
and before_domain_row.ssl_type == after_domain_row.ssl_type:
pass
else:
if after_domain_row.auto_update:
domain_service.update_domain_row(after_domain_row)
@auth_service.permission(role=RoleEnum.USER)
def update_domain_expire_monitor_by_id():
"""
更新监控状态
:return:
"""
current_user_id = g.user_id
domain_id = request.json.get('domain_id')
# data check
domain_row = DomainModel.select().where(
DomainModel.id == domain_id
).first()
if not domain_row:
raise DataNotFoundAppException()
# edit permission check
has_permission = domain_service.has_edit_permission(
domain_row=domain_row,
user_id=current_user_id
)
if not has_permission:
raise ForbiddenAppException()
data = {
"is_monitor": request.json.get('is_monitor', True)
}
DomainModel.update(
data
).where(
DomainModel.id == domain_row.id
).execute()
@auth_service.permission(role=RoleEnum.USER)
@operation_service.operation_log_decorator(
model=DomainModel,
operation_type_id=OperationEnum.UPDATE,
primary_key='domain_id'
)
def update_domain_field_by_id():
"""
更新单个数据
:return:
"""
current_user_id = g.user_id
domain_id = request.json['domain_id']
field = request.json.get('field')
value = request.json.get('value')
if field not in ['auto_update']:
raise AppException("not allow field")
data = {
field: value,
}
# data check
domain_row = DomainModel.select().where(
DomainModel.id == domain_id
).first()
if not domain_row:
raise DataNotFoundAppException()
# edit permission check
has_permission = domain_service.has_edit_permission(
domain_row=domain_row,
user_id=current_user_id
)
if not has_permission:
raise ForbiddenAppException()
DomainModel.update(data).where(
DomainModel.id == domain_row.id
).execute()
@auth_service.permission(role=RoleEnum.USER)
def update_domain_field_by_ids():
"""
批量更新单个字段值
:return:
"""
current_user_id = g.user_id
domain_ids = request.json['domain_ids']
field = request.json.get('field')
value = request.json.get('value')
if field not in ['auto_update']:
raise AppException("not allow field")
data = {
field: value,
}
DomainModel.update(data).where(
DomainModel.id.in_(domain_ids),
DomainModel.user_id == current_user_id
).execute()
@auth_service.permission(role=RoleEnum.USER)
@operation_service.operation_log_decorator(
model=DomainModel,
operation_type_id=OperationEnum.DELETE,
primary_key='id'
)
def delete_domain_by_id():
"""
删除
:return:
"""
current_user_id = g.user_id
domain_id = request.json['id']
# domain_service.check_permission_and_get_row(domain_id, current_user_id)
# data check
domain_row = DomainModel.select().where(
DomainModel.id == domain_id
).first()
if not domain_row:
raise DataNotFoundAppException()
# edit permission check
has_permission = domain_service.has_edit_permission(
domain_row=domain_row,
user_id=current_user_id
)
if not has_permission:
raise ForbiddenAppException()
DomainModel.delete_by_id(domain_row.id)
# 同时移除主机信息
AddressModel.delete().where(
AddressModel.domain_id == domain_id
).execute()
@auth_service.permission(role=RoleEnum.USER)
@operation_service.operation_log_decorator(
model=DomainModel,
operation_type_id=OperationEnum.BATCH_DELETE,
primary_key='ids'
)
def delete_domain_by_ids():
"""
批量删除
@since v1.2.16
:return:
"""
current_user_id = g.user_id
domain_ids = request.json['ids']
DomainModel.delete().where(
DomainModel.id.in_(domain_ids),
DomainModel.user_id == current_user_id
).execute()
# 同时移除主机信息
AddressModel.delete().where(
AddressModel.domain_id.in_(domain_ids)
).execute()
@auth_service.permission(role=RoleEnum.USER)
def get_domain_by_id():
"""
获取
:return:
"""
current_user_id = g.user_id
domain_id = request.json.get('domain_id') or request.json['id']
# row = domain_service.check_permission_and_get_row(domain_id, current_user_id)
# row = DomainModel.get_by_id(domain_id)
# data check
domain_row = DomainModel.select().where(
DomainModel.id == domain_id
).first()
if not domain_row:
raise DataNotFoundAppException()
# edit permission check
has_permission = domain_service.has_edit_permission(
domain_row=domain_row,
user_id=current_user_id
)
if not has_permission:
raise ForbiddenAppException()
row = model_to_dict(
model=domain_row,
extra_attrs=[
'real_time_expire_days',
'domain_url',
'update_time_label',
'expire_status',
]
)
# 主机数量
address_count = AddressModel.select().where(
AddressModel.domain_id == domain_id
).count()
row['address_count'] = address_count
row['group_name'] = group_service.get_group_name_by_id(row['group_id'])
if row['user_id'] == current_user_id:
has_edit_permission = True
else:
first_row = GroupUserModel.select().where(
GroupUserModel.group_id == row['group_id'],
GroupUserModel.user_id == current_user_id,
GroupUserModel.has_edit_permission == True
).first()
if first_row:
has_edit_permission = True
else:
has_edit_permission = False
row['has_edit_permission'] = has_edit_permission
return row
@auth_service.permission(role=RoleEnum.USER)
def update_all_domain_cert_info():
"""
更新所有域名证书信息
:return:
"""
domain_service.update_all_domain_cert_info()
@auth_service.permission(role=RoleEnum.USER)
def update_all_domain_cert_info_of_user():
"""
更新当前用户的所有域名信息
:return:
"""
current_user_id = g.user_id
domain_service.update_all_domain_cert_info_of_user(user_id=current_user_id)
# async_task_service.submit_task(fn=domain_service.update_all_domain_cert_info_of_user, user_id=current_user_id)
@auth_service.permission(role=RoleEnum.USER)
def update_domain_row_info_by_id():
"""
更新域名关联的证书信息
:return:
@since v1.3.1
"""
current_user_id = g.user_id
# @since v1.2.24 支持参数 domain_id
domain_id = request.json.get('domain_id') or request.json['id']
# row = domain_service.check_permission_and_get_row(domain_id, current_user_id)
# row = DomainModel.get_by_id(domain_id)
# data check
domain_row = DomainModel.select().where(
DomainModel.id == domain_id
).first()
if not domain_row:
raise DataNotFoundAppException()
# edit permission check
has_permission = domain_service.has_read_permission(
domain_row=domain_row,
user_id=current_user_id
)
if not has_permission:
raise ForbiddenAppException()
domain_service.update_domain_row(domain_row=domain_row)
@auth_service.permission(role=RoleEnum.USER)
def get_all_domain_list_of_user():
"""
获取用户的所有域名数据
:return:
"""
current_user_id = g.user_id
# temp_filename = domain_service.export_domain_to_file(current_user_id)
rows = DomainModel.select().where(
DomainModel.user_id == current_user_id
)
lst = [{'domain': row.domain} for row in rows]
return {
'list': lst,
'total': len(lst)
}
@auth_service.permission(role=RoleEnum.USER)
def import_domain_from_file():
"""
从文件导入域名
支持 txt 和 csv格式
:return:
"""
current_user_id = g.user_id
update_file = request.files.get('file')
filename = file_service.save_temp_file(update_file)
# 导入数据
domain_service.add_domain_from_file(filename, current_user_id)
# 异步导入
# async_task_service.submit_task(fn=domain_service.add_domain_from_file, filename=filename, user_id=current_user_id)
# 异步查询
domain_service.update_all_domain_cert_info_of_user(user_id=current_user_id)
# async_task_service.submit_task(fn=domain_service.update_all_domain_cert_info_of_user, user_id=current_user_id)
@auth_service.permission(role=RoleEnum.USER)
def export_domain_file():
"""
导出域名文件
csv格式
:return:
"""
current_user_id = g.user_id
keyword = request.json.get('keyword')
group_id = request.json.get('group_id')
group_ids = request.json.get('group_ids')
expire_days = request.json.get('expire_days')
role = request.json.get('role')
ext = request.json.get('ext', 'csv')
order_prop = request.json.get('order_prop') or 'expire_days'
order_type = request.json.get('order_type') or 'ascending'
params = {
'keyword': keyword,
'group_id': group_id,
'group_ids': group_ids,
'expire_days': expire_days,
'role': role,
'user_id': current_user_id,
}
query = domain_service.get_domain_list_query(**params)
ordering = domain_service.get_domain_ordering(order_prop=order_prop, order_type=order_type)
rows = query.order_by(*ordering)
# 分组名
lst = list(map(lambda m: model_to_dict(
model=m,
extra_attrs=[
'start_date',
'expire_date',
'expire_days',
'create_time_label',
'real_time_expire_days',
'real_time_ssl_total_days',
'real_time_ssl_expire_days',
'domain_url',
'update_time_label',
'expire_status',
]
), rows))
group_service.load_group_name(lst)
filename = domain_service.export_domain_to_file(rows=lst, ext=ext)
return {
'name': filename,
'url': file_service.resolve_temp_url(filename)
}
@auth_service.permission(role=RoleEnum.USER)
def domain_relation_group():
"""
分组关联域名
:return:
"""
current_user_id = g.user_id
# temp_filename = domain_service.export_domain_to_file(current_user_id)
domain_ids = request.json['domain_ids']
group_id = request.json['group_id']
DomainModel.update(
group_id=group_id
).where(
DomainModel.id.in_(domain_ids),
DomainModel.user_id == current_user_id
).execute()
@auth_service.permission(role=RoleEnum.USER)
def get_domain_list():
"""
获取域名列表
:return:
"""
current_user_id = g.user_id
page = request.json.get('page', 1)
size = request.json.get('size', 10)
keyword = request.json.get('keyword')
group_id = request.json.get('group_id')
group_ids = request.json.get('group_ids')
expire_days = request.json.get('expire_days')
role = request.json.get('role')
order_prop = request.json.get('order_prop') or 'expire_days'
order_type = request.json.get('order_type') or 'ascending'
# 组权限
group_user_permission_map = {}
if role == RoleEnum.ADMIN:
pass
else:
# 所在分组
group_user_rows = GroupUserModel.select().where(
GroupUserModel.user_id == current_user_id
)
group_user_list = list(group_user_rows)
# 组员权限
group_user_permission_map = {row.group_id: row.has_edit_permission for row in group_user_list}
params = {
'keyword': keyword,
'group_id': group_id,
'group_ids': group_ids,
'expire_days': expire_days,
'role': role,
'user_id': current_user_id,
}
query = domain_service.get_domain_list_query(**params)
ordering = domain_service.get_domain_ordering(order_prop=order_prop, order_type=order_type)
total = query.count()
lst = []
if total > 0:
lst = query.order_by(*ordering).paginate(page, size)
lst = list(map(lambda m: model_to_dict(
model=m,
extra_attrs=[
'expire_days',
'create_time_label',
'real_time_expire_days',
'real_time_ssl_total_days',
'real_time_ssl_expire_days',
'domain_url',
'update_time_label',
'expire_status',
]
), lst))
# 加载主机数量
domain_service.load_address_count(lst)
# 加载域名过期时间
domain_service.load_domain_expire_days(lst)
# 分组名
group_service.load_group_name(lst)
for row in lst:
if role == RoleEnum.ADMIN:
has_edit_permission = True
elif row['user_id'] == current_user_id:
has_edit_permission = True
else:
has_edit_permission = group_user_permission_map.get(row['group_id'], False)
row['has_edit_permission'] = has_edit_permission
# lst = model_util.list_with_relation_one(lst, 'group', GroupModel)
return {
'list': lst,
'total': total
}
@auth_service.permission(role=RoleEnum.USER)
def get_domain_group_filter():
"""
获取证书分组筛选器
:return:
"""
current_user_id = g.user_id
# 分组列表数据
query = GroupModel.select().where(
GroupModel.user_id == current_user_id
)
# 所在分组
group_user_rows = GroupUserModel.select().where(
GroupUserModel.user_id == current_user_id
)
group_user_list = list(group_user_rows)
user_group_ids = [row.group_id for row in group_user_list]
if user_group_ids:
query = query.orwhere(GroupModel.id.in_(user_group_ids))
total = query.count()
lst = []
if total > 0:
lst = [model_to_dict(row) for row in query]
group_ids = [row['id'] for row in lst]
# 证书分组统计
cert_groups = DomainModel.select(
DomainModel.group_id,
fn.COUNT(DomainModel.id).alias('count')
).where(
DomainModel.group_id.in_(group_ids)
).group_by(DomainModel.group_id)
cert_groups_map = {
str(row.group_id): row.count
for row in cert_groups
}
for row in lst:
row['cert_count'] = cert_groups_map.get(str(row['id']), 0)
# leader
if row['user_id'] == current_user_id:
row['is_leader'] = True
else:
row['is_leader'] = False
# if cert_groups_map.get('0'):
# lst.append({
# 'cert_count': cert_groups_map.get('0'),
# 'id': 0,
# 'name': '未分组',
# })
lst.sort(key=itemgetter('cert_count'), reverse=True)
return {
'list': lst,
'total': len(lst),
}