diff --git a/src/api/v1/asset.py b/src/api/v1/asset.py index de4b111..152a713 100644 --- a/src/api/v1/asset.py +++ b/src/api/v1/asset.py @@ -2,6 +2,9 @@ from flask import Blueprint from flask_restful import Api from controller.asset import views +from controller.asset import instance +from controller.asset import middleware +from controller.asset import credential # 当前app的蓝图,以app名为前缀 asset = Blueprint('asset', __name__, url_prefix="/asset") @@ -11,17 +14,19 @@ api = Api(asset) api.add_resource(views.HostViews, '/host/', endpoint="host") api.add_resource(views.HostDetailViews, '/host//', endpoint="host-detail") -api.add_resource(views.MySQLInstanceViews, '/database/mysql/', endpoint="db-mysql") -api.add_resource(views.MySQLInstanceDetail, '/database/mysql//', endpoint="db-mysql-detail") -# 数据实例中的库详情 -api.add_resource(views.DatabaseViews, '/database//db/', endpoint="db-database") -api.add_resource(views.DatabaseDetailViews, '/database//db//', endpoint="db-database-detail") +# 凭据相关视图 +api.add_resource(credential.CredentialViews, '/credential/', endpoint="cred-all") +api.add_resource(credential.CredentialClassViews, '/credential//', endpoint="cred-cls-list") +api.add_resource(credential.CredentialClassDetail, + '/credential///', endpoint="cred-cls-detail") -api.add_resource(views.RedisInstanceViews, '/database/redis/', endpoint="db-redis") -api.add_resource(views.RedisInstanceDetail, '/database/redis//', endpoint="db-redis-detail") +# 中间件相关视图 +api.add_resource(middleware.MiddlewareViews, '/middleware/', endpoint="mdw-all") +api.add_resource(middleware.MiddlewareClassViews, '/middleware//', endpoint="mdw-cls-list") +api.add_resource(middleware.MiddlewareClassDetail, + '/middleware///', endpoint="mdw-cls-detail") -api.add_resource(views.NginxInstanceViews, '/middleware/nginx/', endpoint="middleware-nginx") -api.add_resource(views.NginxInstanceDetail, '/middleware/nginx//', endpoint="middleware-nginx-detail") - -api.add_resource(views.CDNViews, '/cdn/', endpoint="cdn") -api.add_resource(views.CDNDetail, '/cdn//', endpoint="cdn-detail") +# 实例相关视图 +api.add_resource(instance.InstanceViews, '/instance/', endpoint="ins-all") +api.add_resource(instance.InstanceClassViews, '/instance//', endpoint="ins-cls-list") +api.add_resource(instance.InstanceClassDetail, '/instance///', endpoint="ins-cls-detail") diff --git a/src/common/formatter.py b/src/common/formatter.py new file mode 100644 index 0000000..56c0bd4 --- /dev/null +++ b/src/common/formatter.py @@ -0,0 +1,31 @@ +from flask_restful import marshal +from common.serializer import marshal as marshal_fields +from functools import reduce + + +def merge_dict_reduce(x: dict, y: dict): + """合并字典""" + x.update(y) + return x + + +def key_indexed_map(dataset: list, fields, key="id"): + """将数据集合转换为以指定列为 `key` 的数据,可以指定 `key` 为某个字段名 + 如:{ + "xx01": {"id": "xx01", "field_1": "1", ...}, + "xx02": {"id": "xx02", "field_1": "1", ...}, + } + """ + def pk_indexed_map(obj): + """需要是个闭包,使用传入的参数""" + idx = str(getattr(obj, key)) if hasattr(obj, key) else "" + return {idx: marshal(obj, fields)} + + return reduce(merge_dict_reduce, map(pk_indexed_map, dataset)) + + +def to_list(dataset: list, fields, only_exist=True, **kwargs): + """将数据集合转换为 `list` 对象,按 fields 字段描述转换""" + if only_exist: + return list(map(lambda x: marshal_fields(x, fields, only_exist=True, **kwargs), dataset)) + return list(map(lambda x: marshal(x, fields, **kwargs), dataset)) diff --git a/src/common/serializer.py b/src/common/serializer.py new file mode 100644 index 0000000..a1d323c --- /dev/null +++ b/src/common/serializer.py @@ -0,0 +1,60 @@ +from collections import OrderedDict + + +def marshal(data, fields, envelope=None, only_exist=False, required_fields=None, **kwargs): + """Takes raw data (in the form of a dict, list, object) and a dict of + fields to output and filters the data based on those fields. + + :param data: the actual object(s) from which the fields are taken from + :param fields: a dict of whose keys will make up the final serialized + response output + :param envelope: optional key that will be used to envelop the serialized + response + :param only_exist: 只渲染存在的 key + :param required_fields: 必须渲染的 key,不会被 only_exist 排除 + + + >>> from flask_restful import fields, marshal + >>> data = { 'a': 100, 'b': 'foo' } + >>> mfields = { 'a': fields.Raw } + + >>> marshal(data, mfields) + OrderedDict([('a', 100)]) + + >>> marshal(data, mfields, envelope='data') + OrderedDict([('data', OrderedDict([('a', 100)]))]) + + """ + + if required_fields is None: + required_fields = [] + + def make(cls): + if isinstance(cls, type): + return cls() + return cls + + if isinstance(data, (list, tuple)): + return (OrderedDict([(envelope, [marshal(d, fields) for d in data])]) + if envelope else [marshal(d, fields) for d in data]) + + if not only_exist: + items = ((k, marshal(data, v) if isinstance(v, dict) + else make(v).output(k, data)) + for k, v in fields.items()) + else: + def parse(item): + k, v = item + if isinstance(v, dict): + return k, marshal(data, v) + if hasattr(data, k) or k in required_fields: + return k, make(v).output(k, data) + return None, "" + # items = ((k, marshal(data, v) if isinstance(v, dict) + # else make(v).output(k, data)) + # for k, v in fields.items()) + items = ((k, v) for k, v in map(parse, fields.items()) if k is not None) + print(items) + return OrderedDict([(envelope, OrderedDict(items))]) if envelope else OrderedDict(items) + + diff --git a/src/common/views.py b/src/common/views.py index 7c5e84b..dce4076 100644 --- a/src/common/views.py +++ b/src/common/views.py @@ -4,6 +4,7 @@ from typing import List, Dict from flask import request from flask_restful import abort, Resource, marshal, fields, reqparse +from common import formatter from common.utils import abort_response from common.crypto import quick_crypto @@ -60,9 +61,9 @@ class ModelViewBase(Resource): # method_decorators = [token_header_required, ] method_decorators = [] - def get_object(self, pk): + def get_object(self, pk, **kwargs): try: - return self.model.objects(id=pk).first_or_404(message="resource not found") + return self.model.objects(id=pk, **kwargs).first_or_404(message="resource not found") except: abort(404, msg=f"resource '{pk}' not found") @@ -117,7 +118,7 @@ class ModelViewBase(Resource): if errors: abort_response(400, 1001, msg=f"部分字段需要唯一,请检查重复!", errors=errors) - def validate_fields(self, args: dict, create=True) -> dict: + def validate_fields(self, args: dict, create=True, **kwargs) -> dict: return args @staticmethod @@ -163,7 +164,7 @@ class ListMixin(ModelViewBase): # filter 过滤的字段参数 filter_fields = [] - def get_queryset(self): + def get_queryset(self, *args, **kwargs): self.queryset = self.model.objects def filter_queryset(self): @@ -197,21 +198,23 @@ class ListMixin(ModelViewBase): logger.exception(f"查询出错 {self.model} query_params={query_params}") return queryset - def get(self): + def get(self, *args, **kwargs): """获取列表数据""" # 过滤后的数据, - self.get_queryset() + self.get_queryset(*args, **kwargs) - # TODO 匹配过滤参数 self.queryset = self.filter_queryset() - - if self.paging: + limit = request.args.get("limit") + if self.paging and limit != "0": return self.paginate_queryset(self. queryset) - # TODO 这里还未了解到比较妥的办法,似乎 `marshal` 返回的信息必须是 `dict`,返回铺平对象的 `[{obj}, {obj}]` 没找到方法,待研究 - # 不分页,需要取出对象,铺平 - return [marshal(obj, self.fields) for obj in self.queryset] + # 转为对象列表 + if self.queryset.count() > 0: + return formatter.to_list(self.queryset, self.fields, only_exist=True) + # return marshal(self.queryset, self.fields, envelope="id") + # return formatter.key_indexed_map(self.queryset, self.fields) + return [] class CreateMixin(ModelViewBase): @@ -227,7 +230,7 @@ class CreateMixin(ModelViewBase): # 解析参数 args = self.request_parse.parse_args() - validated_data = self.validate_fields(args, create=True) + validated_data = self.validate_fields(args, create=True, **kwargs) # 校验关联字段 self.validate_relation_fields(validated_data) @@ -319,6 +322,67 @@ class DetailViewSet(RetrieveMixin, UpdateMixin, DestroyMixin): """带 `pk` 参数的视图集合""" +class RetrieveClassMixin(ModelViewBase): + + def get(self, class_name, pk): + obj = self.get_object(pk, class_name=class_name) + return marshal(obj, self.fields) + + +class UpdateClassMixin(ModelViewBase): + + def pre_update(self, obj, args: dict): + """更新前钩子""" + data = {} + for k, v in args.items(): + # 为 None 的不设置 + if v is not None: + data[k] = v + return data + + def put(self, class_name, pk): + # 获取对象 + obj = self.get_object(pk, class_name=class_name) + + # 解析参数 + args = self.request_parse.parse_args() + validated_data = self.validate_fields(args, create=False) + # 校验关联字段 + self.validate_relation_fields(validated_data) + validated_data = self.pre_update(obj, validated_data) + + # 检查重复:检查提交的唯一字段值,是否有存在的对象,与当前对象id不同认为异常 + self.validate_uniq_fields(args, obj=obj) + + # 更新对象、保存 + try: + obj.update(**validated_data) + obj.save() + # 重新读取数据 + obj.reload() + except Exception as e: + logger.exception(f"{self.model} 保存对象失败!pk={pk} data={args}") + abort_response(500, 1500, msg=f"保存对象失败!{str(e)}") + return + + return marshal(obj, self.fields) + + +class DestroyClassMixin(ModelViewBase): + + def pre_destroy(self, obj): + """删除对象前的方法,可以在这拦截做些操作""" + pass + + def delete(self, class_name, pk): + """删除对象方法""" + obj = self.get_object(pk, class_name=class_name) + # 删除前钩子 + self.pre_destroy(obj) + obj.delete() + return {"id": pk} + + class EncryptRequiredCreateView(CreateMixin): """创建对象时,加密字段的视图""" # 需要加密的字段 diff --git a/src/controller/asset/credential.py b/src/controller/asset/credential.py new file mode 100644 index 0000000..4b5a9e3 --- /dev/null +++ b/src/controller/asset/credential.py @@ -0,0 +1,80 @@ +from flask import request +from flask_restful import reqparse + +from models.asset.credential import Credential +from models.asset.credentialFields import CredentialFields, CredentialDetailFields +from common.views import (ListCreateViewSet, DetailViewSet, + RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin) +from common.utils import abort_response + + +class CredentialParse: + """凭据信息的校验类""" + request_parse = None + # 给子类用的唯一字段,用于校验 + # uniq_fields = ("name", "host") + + def init_parse(self): + self.request_parse = reqparse.RequestParser() + + self.request_parse.add_argument("class_name", required=False, type=str, location='json') + + self.request_parse.add_argument("extra", required=False, type=dict, location='json') + self.request_parse.add_argument("data", required=False, type=dict, location='json') + self.request_parse.add_argument("tags", required=False, type=list, location='json') + self.request_parse.add_argument("labels", required=False, type=dict, location='json') + + +class CredentialClassNameValidate: + """统一校验 class_name""" + def validate_fields(self, args: dict, create=False, **kwargs) -> dict: + class_name = kwargs.get("class_name") + class_name_params = args.get("class_name") + if class_name_params and class_name_params != class_name: + return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!") + args["class_name"] = class_name + return args + + +class CredentialViews(CredentialParse, ListCreateViewSet): + """凭据列表、创建视图""" + model = Credential + fields = CredentialFields + filter_fields = (("name", "icontains"), ) + + def __init__(self): + self.init_parse() + print(request.method) + self.request_parse.add_argument("name", required=True, type=str, location='json') + + +class CredentialDetail(CredentialParse, CredentialClassNameValidate, DetailViewSet): + """凭据详情""" + model = Credential + fields = CredentialDetailFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=False, type=str, location='json') + + +class CredentialClassViews(CredentialParse, CredentialClassNameValidate, ListCreateViewSet): + """凭据列表,带 class_name 参数""" + model = Credential + fields = CredentialFields + filter_fields = (("name", "icontains"),) + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=True, type=str, location='json') + + +class CredentialClassDetail(CredentialParse, CredentialClassNameValidate, + RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin): + """凭据详情,带 class_name 和 pk 参数""" + model = Credential + fields = CredentialDetailFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=False, type=str, location='json') diff --git a/src/controller/asset/instance.py b/src/controller/asset/instance.py new file mode 100644 index 0000000..1871250 --- /dev/null +++ b/src/controller/asset/instance.py @@ -0,0 +1,94 @@ +from flask_restful import reqparse + +from models.asset.middleware import Middleware +from models.asset.instance import Instance +from models.asset.instanceFields import InstanceFields, InstanceDetailFields +from common.views import (ListCreateViewSet, DetailViewSet, + RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin) +from common.utils import abort_response + + +class InstanceParse: + """ + 中间件模型公共字段的校验类 + """ + request_parse = None + # 给子类用的唯一字段,用于校验 + uniq_fields = (("name", "class_name"),) + + def init_parse(self): + self.request_parse = reqparse.RequestParser() + self.request_parse.add_argument("class_name", required=False, type=str, location='json') + self.request_parse.add_argument("middleware_id", required=False, type=str, location='json') + self.request_parse.add_argument("credentials", required=False, type=list, location='json') + + self.request_parse.add_argument("extra", required=False, type=dict, location='json') + self.request_parse.add_argument("data", required=False, type=dict, location='json') + self.request_parse.add_argument("tags", required=False, type=list, location='json') + self.request_parse.add_argument("labels", required=False, type=dict, location='json') + + +class InstanceClassNameValidate: + """统一校验 class_name和关联的中间件,若中间件 ID 设置值,class_name 需要相同""" + def validate_fields(self, args: dict, create=False, **kwargs) -> dict: + # 校验 class_name,非必填参数,但是在URL中有传递 + # 若URL中的参数与请求体中的参数不一致,将抛错(最好是请求体中不带,以URL中的为准) + class_name = kwargs.get("class_name") + class_name_params = args.get("class_name") + if class_name_params and class_name_params != class_name: + return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!") + args["class_name"] = class_name + + # 校验 middleware_id 若设置值,检查 middleware 对象是否存在,且类型与 instance 一致 + middleware_id = args.get("middleware_id") + if middleware_id: + try: + assert Middleware.objects(id=middleware_id, class_name=class_name).first() + except: + return abort_response(400, 1400, msg="参数中的同类型 middleware 对象不存在") + return args + + +class InstanceViews(InstanceParse, ListCreateViewSet): + model = Instance + fields = InstanceFields + filter_fields = (("name", "icontains"), ("class_name", "")) + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=True, type=str, location='json') + + +class InstanceDetailViews(InstanceParse, DetailViewSet): + model = Instance + fields = InstanceDetailFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=False, type=str, location='json') + + +class InstanceClassViews(InstanceParse, InstanceClassNameValidate, ListCreateViewSet): + """某类别的实例列表""" + model = Instance + fields = InstanceFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=True, type=str, location='json') + + def get_queryset(self, *args, **kwargs): + self.queryset = self.model.objects(**kwargs) + + +class InstanceClassDetail(InstanceParse, InstanceClassNameValidate, RetrieveClassMixin, UpdateClassMixin, DestroyClassMixin): + """某类别的实例详情""" + model = Instance + fields = InstanceDetailFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=False, type=str, location='json') + + def get_queryset(self, *args, **kwargs): + self.queryset = self.model.objects(**kwargs) diff --git a/src/controller/asset/middleware.py b/src/controller/asset/middleware.py new file mode 100644 index 0000000..d1007f9 --- /dev/null +++ b/src/controller/asset/middleware.py @@ -0,0 +1,90 @@ +from flask_restful import reqparse + +from models.asset.middleware import Middleware +from models.asset.middlewareFields import MiddlewareFields +from common.utils import abort_response +from common.views import (ListCreateViewSet, ListMixin, DetailViewSet, + RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin) + + +class MiddlewareParse: + """ + 中间件模型公共字段的校验类 + """ + request_parse = None + # 给子类用的唯一字段,用于校验 + uniq_fields = ("name", "host") + + def init_parse(self): + self.request_parse = reqparse.RequestParser() + + self.request_parse.add_argument("class_name", required=False, type=str, location='json') + self.request_parse.add_argument("host", required=False, type=str, location='json') + self.request_parse.add_argument("port", required=False, type=int, location='json') + self.request_parse.add_argument("manage", required=False, type=str, location='json') + + self.request_parse.add_argument("extra", required=False, type=dict, location='json') + self.request_parse.add_argument("data", required=False, type=dict, location='json') + self.request_parse.add_argument("tags", required=False, type=list, location='json') + self.request_parse.add_argument("labels", required=False, type=dict, location='json') + + +class MiddlewareClassNameValidate: + """统一校验 class_name""" + def validate_fields(self, args: dict, create=False, **kwargs) -> dict: + class_name = kwargs.get("class_name") + class_name_params = args.get("class_name") + if class_name_params and class_name_params != class_name: + return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!") + args["class_name"] = class_name + return args + + +class MiddlewareViews(MiddlewareParse, ListCreateViewSet): + model = Middleware + fields = MiddlewareFields + uniq_fields = ("name",) + filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", "icontains"), ("class_name", "")) + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=True, type=str, location='json') + + +class MiddlewareDetail(MiddlewareParse, DetailViewSet): + """分类别的视图""" + model = Middleware + fields = MiddlewareFields + uniq_fields = ("name",) + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=True, type=str, location='json') + + +class MiddlewareClassViews(MiddlewareParse, MiddlewareClassNameValidate, ListCreateViewSet): + """按分类的中间件视图,带 class_name 参数""" + model = Middleware + fields = MiddlewareFields + uniq_fields = ("name",) + filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", "icontains")) + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=True, type=str, location='json') + + def get_queryset(self, *args, **kwargs): + """按分类查找""" + self.queryset = self.model.objects(**kwargs) + + +class MiddlewareClassDetail(MiddlewareParse, MiddlewareClassNameValidate, + RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin): + """分类别的详情视图,带 class_name 和 pk 参数""" + model = Middleware + fields = MiddlewareFields + uniq_fields = ("name",) + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=False, type=str, location='json') diff --git a/src/models/asset/credential.py b/src/models/asset/credential.py new file mode 100644 index 0000000..cac4918 --- /dev/null +++ b/src/models/asset/credential.py @@ -0,0 +1,13 @@ +import mongoengine as mongo +from common.document import DocumentBase + + +class Credential(DocumentBase): + """凭据信息""" + meta = {'allow_inheritance': True, 'collection': 'credential'} + class_name = mongo.StringField(required=False, default="") + name = mongo.StringField(required=True) + extra = mongo.DictField(required=False) + data = mongo.DictField(default=dict) + tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 + labels = mongo.DictField(default=dict) diff --git a/src/models/asset/credentialFields.py b/src/models/asset/credentialFields.py new file mode 100644 index 0000000..be82e91 --- /dev/null +++ b/src/models/asset/credentialFields.py @@ -0,0 +1,15 @@ +from flask_restful import fields + + +CredentialFields = { + "id": fields.String, + "name": fields.String, + "class_name": fields.String, + "extra": fields.Raw, + "credentials": fields.List(fields.String), + "data": fields.Raw, + "tags": fields.List(fields.String), + "labels": fields.Raw, +} + +CredentialDetailFields = CredentialFields diff --git a/src/models/asset/fields.py b/src/models/asset/fields.py index 2510131..e6e2ba5 100644 --- a/src/models/asset/fields.py +++ b/src/models/asset/fields.py @@ -38,6 +38,18 @@ DatabaseServerFields = { "labels": fields.Raw, } + +databaseDetailFields = { + "id": "", + "name": fields.String, + "domain": fields.String, + "host": fields.String, + "manage": fields.String, + "data": fields.Raw, + "tags": fields.List(fields.String), + "labels": fields.Raw, +} + DatabaseFields = { "id": fields.String, "name": fields.String, diff --git a/src/models/asset/instance.py b/src/models/asset/instance.py new file mode 100644 index 0000000..8626856 --- /dev/null +++ b/src/models/asset/instance.py @@ -0,0 +1,35 @@ +import mongoengine as mongo + +from common.document import DocumentBase +from models.asset.middleware import Middleware + + +class Instance(DocumentBase): + """""" + meta = {'allow_inheritance': True, 'collection': 'instance'} + # 实例的分类名称 + class_name = mongo.StringField(required=False, default="") + + # 实例的名称,可能是库名、仓库名等 + name = mongo.StringField(required=True) + + # 关联的 middleware + middleware_id = mongo.ObjectIdField(required=False) + + # 对应可以有多个凭据 + credentials = mongo.ListField(mongo.ObjectIdField, default=list) + # 按类型不同的信息存放到 extra 字典 + extra = mongo.DictField(default=dict) + + data = mongo.DictField(default=dict) + tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 + labels = mongo.DictField(default=dict) + + @property + def middleware(self): + if self.middleware_id: + try: + return Middleware.objects(id=self.middleware_id).first() + except: + pass + return None diff --git a/src/models/asset/instanceFields.py b/src/models/asset/instanceFields.py new file mode 100644 index 0000000..9483e58 --- /dev/null +++ b/src/models/asset/instanceFields.py @@ -0,0 +1,30 @@ +from flask_restful import fields + +from models.asset.middlewareFields import MiddlewareFields + +# 普通视图,只返回内部字段 +InstanceFields = { + "id": fields.String, + "name": fields.String, + "class_name": fields.String, + "middleware_id": fields.String, + + "extra": fields.Raw, + "data": fields.Raw, + "tags": fields.List(fields.String), + "labels": fields.Raw, +} + +# 详情视图,返回中间件和凭据 +InstanceDetailFields = { + "id": fields.String, + "name": fields.String, + "class_name": fields.String, + "middleware": fields.Nested(MiddlewareFields), + "credentials": fields.List(fields.String), + + "extra": fields.Raw, + "data": fields.Raw, + "tags": fields.List(fields.String), + "labels": fields.Raw, +} diff --git a/src/models/asset/middleware.py b/src/models/asset/middleware.py new file mode 100644 index 0000000..f4fe2a9 --- /dev/null +++ b/src/models/asset/middleware.py @@ -0,0 +1,33 @@ +import mongoengine as mongo + +from common.document import DocumentBase + + +class Middleware(DocumentBase): + """中间件""" + meta = {'allow_inheritance': True, 'collection': 'mdw', 'strict': False} + # 分类名称 + class_name = mongo.StringField(required=False, default="") + + name = mongo.StringField(required=True) + host = mongo.StringField(required=False, default="") + port = mongo.IntField(default=0) + # 管理者,预留 + manage = mongo.StringField(max_length=128, required=False, default="") + extra = mongo.DictField(default=dict) + data = mongo.DictField(default=dict) + # 标记和标签 + tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 + labels = mongo.DictField(default=dict) + + +# class DBServer(Middleware): +# """数据库服务器""" +# meta = {'allow_inheritance': True, 'strict': False} +# domain = mongo.StringField(required=False) # 域名连接的地址 +# credentials = mongo.ListField(mongo.ObjectIdField, default=list) + +# 配置 +# storage = mongo.IntField(required=False, default=0) +# memory = mongo.IntField(required=False, default=0) +# core = mongo.IntField(required=False, default=0) diff --git a/src/models/asset/middlewareFields.py b/src/models/asset/middlewareFields.py new file mode 100644 index 0000000..2715fd9 --- /dev/null +++ b/src/models/asset/middlewareFields.py @@ -0,0 +1,38 @@ +from flask_restful import fields + +MiddlewareFields = { + "id": fields.String, + "name": fields.String, + "host": fields.String, + "port": fields.Integer, + "manage": fields.String, + "class_name": fields.String, + "extra": fields.Raw, + "credentials": fields.List(fields.String), + "data": fields.Raw, + "tags": fields.List(fields.String), + "labels": fields.Raw, +} + +# DatabaseFields = { +# "id": fields.String, +# "name": fields.String, +# "username": fields.String, +# "password": fields.String, +# "data": fields.Raw, +# "tags": fields.List(fields.String), +# "labels": fields.Raw, +# } +# +# DBServerFields = { +# "domain": fields.String, +# "port": fields.Integer, +# "username": fields.String, +# "password": fields.String, +# "storage": fields.Integer, +# "memory": fields.Integer, +# "core": fields.Integer, +# "databases": fields.List(fields.Nested(DatabaseFields)) +# } +# +# DBServerFields.update(MiddlewareFields) diff --git a/src/models/project/fields.py b/src/models/project/fields.py index 3c1b887..539f213 100644 --- a/src/models/project/fields.py +++ b/src/models/project/fields.py @@ -44,6 +44,7 @@ ChannelFields = { "id": fields.String, "project": fields.Nested(ProjectSimpleField), "name": fields.String, + "fullname": fields.String, "spid": fields.String, "is_cross": fields.Boolean, "version": fields.Nested(VersionFields), diff --git a/src/models/project/models.py b/src/models/project/models.py index 1981704..acce387 100644 --- a/src/models/project/models.py +++ b/src/models/project/models.py @@ -81,6 +81,13 @@ class Channel(DocumentBase): if isinstance(val, Project): self.project_id = str(val.id) + @property + def fullname(self): + project = "" + if self.project: + project = self.project.fullname + return f"{self.spid} {project}" + @classmethod def filter_by_project(cls, name, fork, queryset=None): """过滤项目的所有渠道"""