Merge branch 'master' of github.com:veops/cmdb into dev_ui

This commit is contained in:
wang-liang0615 2023-09-04 13:14:35 +08:00
commit 88c9fb5ae3
38 changed files with 332 additions and 280 deletions

1
.gitignore vendored
View File

@ -39,6 +39,7 @@ pip-log.txt
nosetests.xml nosetests.xml
.pytest_cache .pytest_cache
cmdb-api/test-output cmdb-api/test-output
cmdb-api/api/uploaded_files
# Translations # Translations
*.mo *.mo

View File

@ -9,7 +9,8 @@ from inspect import getmembers
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from flask import Flask from flask import Flask
from flask import jsonify, make_response from flask import jsonify
from flask import make_response
from flask.blueprints import Blueprint from flask.blueprints import Blueprint
from flask.cli import click from flask.cli import click
from flask.json.provider import DefaultJSONProvider from flask.json.provider import DefaultJSONProvider

View File

@ -10,8 +10,11 @@ from api.extensions import db
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.const import BUILTIN_KEYWORDS
from api.lib.cmdb.const import CITypeOperateType from api.lib.cmdb.const import CITypeOperateType
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
from api.lib.cmdb.history import CITypeHistoryManager from api.lib.cmdb.history import CITypeHistoryManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
@ -41,7 +44,7 @@ class AttributeManager(object):
ret_key = choice_web_hook.get('ret_key') ret_key = choice_web_hook.get('ret_key')
headers = choice_web_hook.get('headers') or {} headers = choice_web_hook.get('headers') or {}
payload = choice_web_hook.get('payload') or {} payload = choice_web_hook.get('payload') or {}
method = choice_web_hook.get('method', 'GET').lower() method = (choice_web_hook.get('method') or 'GET').lower()
try: try:
res = getattr(requests, method)(url, headers=headers, data=payload).json() res = getattr(requests, method)(url, headers=headers, data=payload).json()
@ -56,15 +59,17 @@ class AttributeManager(object):
return [[i, {}] for i in (res.get(ret_key_list[-1]) or [])] return [[i, {}] for i in (res.get(ret_key_list[-1]) or [])]
except Exception as e: except Exception as e:
current_app.logger.error(str(e)) current_app.logger.error("get choice values failed: {}".format(e))
return [] return []
@classmethod @classmethod
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_web_hook_parse=True): def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_web_hook_parse=True):
if choice_web_hook and isinstance(choice_web_hook, dict) and choice_web_hook_parse: if choice_web_hook:
return cls._get_choice_values_from_web_hook(choice_web_hook) if choice_web_hook_parse:
elif choice_web_hook and not choice_web_hook_parse: if isinstance(choice_web_hook, dict):
return [] return cls._get_choice_values_from_web_hook(choice_web_hook)
else:
return []
choice_table = ValueTypeMap.choice.get(value_type) choice_table = ValueTypeMap.choice.get(value_type)
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id) choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
@ -74,25 +79,25 @@ class AttributeManager(object):
@staticmethod @staticmethod
def add_choice_values(_id, value_type, choice_values): def add_choice_values(_id, value_type, choice_values):
choice_table = ValueTypeMap.choice.get(value_type) choice_table = ValueTypeMap.choice.get(value_type)
if choice_table is None:
return
choice_table.get_by(attr_id=_id, only_query=True).delete()
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete()
db.session.flush()
choice_values = choice_values
for v, option in choice_values: for v, option in choice_values:
table = choice_table(attr_id=_id, value=v, option=option) choice_table.create(attr_id=_id, value=v, option=option, commit=False)
db.session.add(table)
try: try:
db.session.flush() db.session.flush()
except: except Exception as e:
current_app.logger.warning("add choice values failed: {}".format(e))
return abort(400, ErrFormat.invalid_choice_values) return abort(400, ErrFormat.invalid_choice_values)
@staticmethod @staticmethod
def _del_choice_values(_id, value_type): def _del_choice_values(_id, value_type):
choice_table = ValueTypeMap.choice.get(value_type) choice_table = ValueTypeMap.choice.get(value_type)
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete() choice_table and choice_table.get_by(attr_id=_id, only_query=True).delete()
db.session.flush() db.session.flush()
@classmethod @classmethod
@ -115,8 +120,8 @@ class AttributeManager(object):
attrs = attrs[(page - 1) * page_size:][:page_size] attrs = attrs[(page - 1) * page_size:][:page_size]
res = list() res = list()
for attr in attrs: for attr in attrs:
attr["is_choice"] and attr.update(dict(choice_value=cls.get_choice_values( attr["is_choice"] and attr.update(
attr["id"], attr["value_type"], attr["choice_web_hook"]))) dict(choice_value=cls.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])))
attr['is_choice'] and attr.pop('choice_web_hook', None) attr['is_choice'] and attr.pop('choice_web_hook', None)
res.append(attr) res.append(attr)
@ -125,30 +130,31 @@ class AttributeManager(object):
def get_attribute_by_name(self, name): def get_attribute_by_name(self, name):
attr = Attribute.get_by(name=name, first=True) attr = Attribute.get_by(name=name, first=True)
if attr and attr["is_choice"]: if attr.get("is_choice"):
attr.update(dict(choice_value=self.get_choice_values( attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
attr["id"], attr["value_type"], attr["choice_web_hook"])))
return attr return attr
def get_attribute_by_alias(self, alias): def get_attribute_by_alias(self, alias):
attr = Attribute.get_by(alias=alias, first=True) attr = Attribute.get_by(alias=alias, first=True)
if attr and attr["is_choice"]: if attr.get("is_choice"):
attr.update(dict(choice_value=self.get_choice_values( attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
attr["id"], attr["value_type"], attr["choice_web_hook"])))
return attr return attr
def get_attribute_by_id(self, _id): def get_attribute_by_id(self, _id):
attr = Attribute.get_by_id(_id).to_dict() attr = Attribute.get_by_id(_id).to_dict()
if attr and attr["is_choice"]: if attr.get("is_choice"):
attr.update(dict(choice_value=self.get_choice_values( attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
attr["id"], attr["value_type"], attr["choice_web_hook"])))
return attr return attr
def get_attribute(self, key, choice_web_hook_parse=True): def get_attribute(self, key, choice_web_hook_parse=True):
attr = AttributeCache.get(key).to_dict() attr = AttributeCache.get(key).to_dict()
if attr and attr["is_choice"]: if attr.get("is_choice"):
attr.update(dict(choice_value=self.get_choice_values( attr["choice_value"] = self.get_choice_values(
attr["id"], attr["value_type"], attr["choice_web_hook"])), choice_web_hook_parse=choice_web_hook_parse) attr["id"], attr["value_type"], attr["choice_web_hook"], choice_web_hook_parse=choice_web_hook_parse)
return attr return attr
@staticmethod @staticmethod
@ -164,8 +170,9 @@ class AttributeManager(object):
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
name = kwargs.pop("name") name = kwargs.pop("name")
if name in {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}: if name in BUILTIN_KEYWORDS:
return abort(400, ErrFormat.attribute_name_cannot_be_builtin) return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
alias = kwargs.pop("alias", "") alias = kwargs.pop("alias", "")
alias = name if not alias else alias alias = name if not alias else alias
Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name)) Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name))
@ -218,7 +225,8 @@ class AttributeManager(object):
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):
CITypeAttributesCache.clean(i.type_id) CITypeAttributesCache.clean(i.type_id)
def _change_index(self, attr, old, new): @staticmethod
def _change_index(attr, old, new):
from api.lib.cmdb.utils import TableMap from api.lib.cmdb.utils import TableMap
from api.tasks.cmdb import batch_ci_cache from api.tasks.cmdb import batch_ci_cache
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.cmdb.const import CMDB_QUEUE
@ -227,11 +235,11 @@ class AttributeManager(object):
new_table = TableMap(attr=attr, is_index=new).table new_table = TableMap(attr=attr, is_index=new).table
ci_ids = [] ci_ids = []
for i in db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id): for i in old_table.get_by(attr_id=attr.id, to_dict=False):
new_table.create(ci_id=i.ci_id, attr_id=attr.id, value=i.value, flush=True) new_table.create(ci_id=i.ci_id, attr_id=attr.id, value=i.value, flush=True)
ci_ids.append(i.ci_id) ci_ids.append(i.ci_id)
db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id).delete() old_table.get_by(attr_id=attr.id, only_query=True).delete()
try: try:
db.session.commit() db.session.commit()
@ -296,7 +304,7 @@ class AttributeManager(object):
if is_choice and choice_value: if is_choice and choice_value:
self.add_choice_values(attr.id, attr.value_type, choice_value) self.add_choice_values(attr.id, attr.value_type, choice_value)
elif is_choice: elif existed2['is_choice']:
self._del_choice_values(attr.id, attr.value_type) self._del_choice_values(attr.id, attr.value_type)
try: try:
@ -330,24 +338,25 @@ class AttributeManager(object):
ref = CITypeAttribute.get_by(attr_id=_id, to_dict=False, first=True) ref = CITypeAttribute.get_by(attr_id=_id, to_dict=False, first=True)
if ref is not None: if ref is not None:
ci_type = CITypeCache.get(ref.type_id) ci_type = CITypeCache.get(ref.type_id)
return abort(400, ErrFormat.attribute_is_ref_by_type.format(ci_type.alias)) return abort(400, ErrFormat.attribute_is_ref_by_type.format(ci_type and ci_type.alias or ref.type_id))
if attr.uid != current_user.uid and not is_app_admin('cmdb'): if attr.uid != current_user.uid and not is_app_admin('cmdb'):
return abort(403, ErrFormat.cannot_delete_attribute) return abort(403, ErrFormat.cannot_delete_attribute)
if attr.is_choice: if attr.is_choice:
choice_table = ValueTypeMap.choice.get(attr.value_type) choice_table = ValueTypeMap.choice.get(attr.value_type)
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete() # FIXME: session conflict choice_table.get_by(attr_id=_id, only_query=True).delete()
db.session.flush()
AttributeCache.clean(attr)
attr.soft_delete() attr.soft_delete()
AttributeCache.clean(attr)
for i in PreferenceShowAttributes.get_by(attr_id=_id, to_dict=False): for i in PreferenceShowAttributes.get_by(attr_id=_id, to_dict=False):
i.soft_delete() i.soft_delete(commit=False)
for i in CITypeAttributeGroupItem.get_by(attr_id=_id, to_dict=False): for i in CITypeAttributeGroupItem.get_by(attr_id=_id, to_dict=False):
i.soft_delete() i.soft_delete(commit=False)
db.session.commit()
return name return name

View File

@ -240,9 +240,10 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
try: try:
response, _, _, _, _, _ = s.search() response, _, _, _, _, _ = s.search()
for i in response: for i in response:
if current_user.username not in (i.get('rd_duty') or []) and current_user.username not in \ if (current_user.username not in (i.get('rd_duty') or []) and
(i.get('op_duty') or []) and current_user.nickname not in (i.get('rd_duty') or []) and \ current_user.username not in (i.get('op_duty') or []) and
current_user.nickname not in (i.get('op_duty') or []): current_user.nickname not in (i.get('rd_duty') or []) and
current_user.nickname not in (i.get('op_duty') or [])):
return abort(403, ErrFormat.adt_target_expr_no_permission.format( return abort(403, ErrFormat.adt_target_expr_no_permission.format(
i.get("{}_name".format(i.get('ci_type'))))) i.get("{}_name".format(i.get('ci_type')))))
except SearchError as e: except SearchError as e:

View File

@ -34,6 +34,7 @@ class AttributeCache(object):
attr = attr or Attribute.get_by(alias=key, first=True, to_dict=False) attr = attr or Attribute.get_by(alias=key, first=True, to_dict=False)
if attr is not None: if attr is not None:
cls.set(attr) cls.set(attr)
return attr return attr
@classmethod @classmethod
@ -67,6 +68,7 @@ class CITypeCache(object):
ct = ct or CIType.get_by(alias=key, first=True, to_dict=False) ct = ct or CIType.get_by(alias=key, first=True, to_dict=False)
if ct is not None: if ct is not None:
cls.set(ct) cls.set(ct)
return ct return ct
@classmethod @classmethod
@ -98,6 +100,7 @@ class RelationTypeCache(object):
ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key) ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key)
if ct is not None: if ct is not None:
cls.set(ct) cls.set(ct)
return ct return ct
@classmethod @classmethod
@ -133,12 +136,15 @@ class CITypeAttributesCache(object):
attrs = attrs or cache.get(cls.PREFIX_ID.format(key)) attrs = attrs or cache.get(cls.PREFIX_ID.format(key))
if not attrs: if not attrs:
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False) attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
if not attrs: if not attrs:
ci_type = CIType.get_by(name=key, first=True, to_dict=False) ci_type = CIType.get_by(name=key, first=True, to_dict=False)
if ci_type is not None: if ci_type is not None:
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False) attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
if attrs is not None: if attrs is not None:
cls.set(key, attrs) cls.set(key, attrs)
return attrs return attrs
@classmethod @classmethod
@ -155,13 +161,16 @@ class CITypeAttributesCache(object):
attrs = attrs or cache.get(cls.PREFIX_ID2.format(key)) attrs = attrs or cache.get(cls.PREFIX_ID2.format(key))
if not attrs: if not attrs:
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False) attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
if not attrs: if not attrs:
ci_type = CIType.get_by(name=key, first=True, to_dict=False) ci_type = CIType.get_by(name=key, first=True, to_dict=False)
if ci_type is not None: if ci_type is not None:
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False) attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
if attrs is not None: if attrs is not None:
attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs] attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs]
cls.set2(key, attrs) cls.set2(key, attrs)
return attrs return attrs
@classmethod @classmethod
@ -204,10 +213,11 @@ class CITypeAttributeCache(object):
attr = cache.get(cls.PREFIX_ID.format(type_id, attr_id)) attr = cache.get(cls.PREFIX_ID.format(type_id, attr_id))
attr = attr or cache.get(cls.PREFIX_ID.format(type_id, attr_id)) attr = attr or cache.get(cls.PREFIX_ID.format(type_id, attr_id))
if not attr: attr = attr or CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
attr = CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
if attr is not None: if attr is not None:
cls.set(type_id, attr_id, attr) cls.set(type_id, attr_id, attr)
return attr return attr
@classmethod @classmethod

View File

@ -38,8 +38,8 @@ from api.lib.decorator import kwargs_required
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import Lock
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.lib.utils import Lock
from api.models.cmdb import AutoDiscoveryCI from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
@ -67,11 +67,13 @@ class CIManager(object):
@staticmethod @staticmethod
def get_type_name(ci_id): def get_type_name(ci_id):
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id))) ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
return CITypeCache.get(ci.type_id).name return CITypeCache.get(ci.type_id).name
@staticmethod @staticmethod
def get_type(ci_id): def get_type(ci_id):
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id))) ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
return CITypeCache.get(ci.type_id) return CITypeCache.get(ci.type_id)
@staticmethod @staticmethod
@ -93,9 +95,7 @@ class CIManager(object):
res = dict() res = dict()
if need_children: need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
res.update(children)
ci_type = CITypeCache.get(ci.type_id) ci_type = CITypeCache.get(ci.type_id)
res["ci_type"] = ci_type.name res["ci_type"] = ci_type.name
@ -162,14 +162,11 @@ class CIManager(object):
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id))) ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
if valid: valid and cls.valid_ci_only_read(ci)
cls.valid_ci_only_read(ci)
res = dict() res = dict()
if need_children: need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
res.update(children)
ci_type = CITypeCache.get(ci.type_id) ci_type = CITypeCache.get(ci.type_id)
res["ci_type"] = ci_type.name res["ci_type"] = ci_type.name
@ -248,7 +245,7 @@ class CIManager(object):
for i in unique_constraints: for i in unique_constraints:
attr_ids.extend(i.attr_ids) attr_ids.extend(i.attr_ids)
attrs = [AttributeCache.get(i) for i in list(set(attr_ids))] attrs = [AttributeCache.get(i) for i in set(attr_ids)]
id2name = {i.id: i.name for i in attrs if i} id2name = {i.id: i.name for i in attrs if i}
not_existed_fields = list(set(id2name.values()) - set(ci_dict.keys())) not_existed_fields = list(set(id2name.values()) - set(ci_dict.keys()))
if not_existed_fields and ci_id is not None: if not_existed_fields and ci_id is not None:
@ -333,10 +330,6 @@ class CIManager(object):
if exist_policy == ExistPolicy.NEED: if exist_policy == ExistPolicy.NEED:
return abort(404, ErrFormat.ci_not_found.format("{}={}".format(unique_key.name, unique_value))) return abort(404, ErrFormat.ci_not_found.format("{}={}".format(unique_key.name, unique_value)))
from api.lib.cmdb.const import L_CI
if L_CI and len(CI.get_by(type_id=ci_type.id)) > L_CI * 2:
return abort(400, ErrFormat.limit_ci.format(L_CI * 2))
limit_attrs = cls._valid_ci_for_no_read(ci, ci_type) if not _is_admin else {} limit_attrs = cls._valid_ci_for_no_read(ci, ci_type) if not _is_admin else {}
if existed is None: # set default if existed is None: # set default
@ -368,12 +361,12 @@ class CIManager(object):
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id) cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
for k in ci_dict: for k in ci_dict:
if k not in ci_type_attrs_name and k not in ci_type_attrs_alias and \ if k not in ci_type_attrs_name and (
_no_attribute_policy == ExistPolicy.REJECT: k not in ci_type_attrs_alias and _no_attribute_policy == ExistPolicy.REJECT):
return abort(400, ErrFormat.attribute_not_found.format(k)) return abort(400, ErrFormat.attribute_not_found.format(k))
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and \ if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and (
ci_type_attrs_alias.get(k) not in limit_attrs: ci_type_attrs_alias.get(k) not in limit_attrs):
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k)) return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias} ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
@ -486,11 +479,11 @@ class CIManager(object):
unique_key = AttributeCache.get(ci_type.unique_id) unique_key = AttributeCache.get(ci_type.unique_id)
value_table = TableMap(attr=unique_key).table value_table = TableMap(attr=unique_key).table
v = value_table.get_by(attr_id=unique_key.id, v = (value_table.get_by(attr_id=unique_key.id,
value=unique_value, value=unique_value,
to_dict=False, to_dict=False,
first=True) \ first=True) or
or abort(404, ErrFormat.not_found) abort(404, ErrFormat.not_found))
ci = CI.get_by_id(v.ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(v.ci_id))) ci = CI.get_by_id(v.ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(v.ci_id)))
@ -536,6 +529,7 @@ class CIManager(object):
result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"), result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"),
heartbeat_dict.get(i.get("_id"))) for i in res heartbeat_dict.get(i.get("_id"))) for i in res
if i.get("private_ip")] if i.get("private_ip")]
return numfound, result return numfound, result
@staticmethod @staticmethod
@ -655,6 +649,7 @@ class CIManager(object):
return res return res
current_app.logger.warning("cache not hit...............") current_app.logger.warning("cache not hit...............")
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables, excludes=excludes) return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables, excludes=excludes)
@ -680,6 +675,7 @@ class CIRelationManager(object):
ci_type = CITypeCache.get(type_id) ci_type = CITypeCache.get(type_id)
children = CIManager.get_cis_by_ids(list(map(str, ci_type2ci_ids[type_id])), ret_key=ret_key) children = CIManager.get_cis_by_ids(list(map(str, ci_type2ci_ids[type_id])), ret_key=ret_key)
res[ci_type.name] = children res[ci_type.name] = children
return res return res
@staticmethod @staticmethod
@ -856,12 +852,12 @@ class CIRelationManager(object):
:param children: :param children:
:return: :return:
""" """
if parents is not None and isinstance(parents, list): if isinstance(parents, list):
for parent_id in parents: for parent_id in parents:
for ci_id in ci_ids: for ci_id in ci_ids:
cls.add(parent_id, ci_id) cls.add(parent_id, ci_id)
if children is not None and isinstance(children, list): if isinstance(children, list):
for child_id in children: for child_id in children:
for ci_id in ci_ids: for ci_id in ci_ids:
cls.add(ci_id, child_id) cls.add(ci_id, child_id)
@ -875,7 +871,7 @@ class CIRelationManager(object):
:return: :return:
""" """
if parents is not None and isinstance(parents, list): if isinstance(parents, list):
for parent_id in parents: for parent_id in parents:
for ci_id in ci_ids: for ci_id in ci_ids:
cls.delete_2(parent_id, ci_id) cls.delete_2(parent_id, ci_id)

View File

@ -16,7 +16,9 @@ from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.const import CITypeOperateType from api.lib.cmdb.const import CITypeOperateType
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
from api.lib.cmdb.history import CITypeHistoryManager from api.lib.cmdb.history import CITypeHistoryManager
from api.lib.cmdb.relation_type import RelationTypeManager from api.lib.cmdb.relation_type import RelationTypeManager
@ -60,6 +62,7 @@ class CITypeManager(object):
@staticmethod @staticmethod
def get_name_by_id(type_id): def get_name_by_id(type_id):
ci_type = CITypeCache.get(type_id) ci_type = CITypeCache.get(type_id)
return ci_type and ci_type.name return ci_type and ci_type.name
@staticmethod @staticmethod
@ -71,7 +74,7 @@ class CITypeManager(object):
@staticmethod @staticmethod
def get_ci_types(type_name=None): def get_ci_types(type_name=None):
resources = None resources = None
if current_app.config.get('USE_ACL') and not is_app_admin(): if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
resources = set([i.get('name') for i in ACLManager().get_resources("CIType")]) resources = set([i.get('name') for i in ACLManager().get_resources("CIType")])
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name) ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
@ -110,9 +113,6 @@ class CITypeManager(object):
@classmethod @classmethod
@kwargs_required("name") @kwargs_required("name")
def add(cls, **kwargs): def add(cls, **kwargs):
from api.lib.cmdb.const import L_TYPE
if L_TYPE and len(CIType.get_by()) > L_TYPE * 2:
return abort(400, ErrFormat.limit_ci_type.format(L_TYPE * 2))
unique_key = kwargs.pop("unique_key", None) unique_key = kwargs.pop("unique_key", None)
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define) unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
@ -184,6 +184,7 @@ class CITypeManager(object):
def set_enabled(cls, type_id, enabled=True): def set_enabled(cls, type_id, enabled=True):
ci_type = cls.check_is_existed(type_id) ci_type = cls.check_is_existed(type_id)
ci_type.update(enabled=enabled) ci_type.update(enabled=enabled)
return type_id return type_id
@classmethod @classmethod
@ -268,6 +269,7 @@ class CITypeGroupManager(object):
@staticmethod @staticmethod
def add(name): def add(name):
CITypeGroup.get_by(name=name, first=True) and abort(400, ErrFormat.ci_type_group_exists.format(name)) CITypeGroup.get_by(name=name, first=True) and abort(400, ErrFormat.ci_type_group_exists.format(name))
return CITypeGroup.create(name=name) return CITypeGroup.create(name=name)
@staticmethod @staticmethod
@ -354,6 +356,7 @@ class CITypeAttributeManager(object):
attr_dict.pop('choice_web_hook', None) attr_dict.pop('choice_web_hook', None)
result.append(attr_dict) result.append(attr_dict)
return result return result
@staticmethod @staticmethod
@ -541,6 +544,7 @@ class CITypeRelationManager(object):
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"]) ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
ci_type_dict["relation_type"] = relation_inst.relation_type.name ci_type_dict["relation_type"] = relation_inst.relation_type.name
ci_type_dict["constraint"] = relation_inst.constraint ci_type_dict["constraint"] = relation_inst.constraint
return ci_type_dict return ci_type_dict
@classmethod @classmethod
@ -599,8 +603,8 @@ class CITypeRelationManager(object):
@classmethod @classmethod
def delete(cls, _id): def delete(cls, _id):
ctr = CITypeRelation.get_by_id(_id) or \ ctr = (CITypeRelation.get_by_id(_id) or
abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id))) abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id))))
ctr.soft_delete() ctr.soft_delete()
CITypeHistoryManager.add(CITypeOperateType.DELETE_RELATION, ctr.parent_id, CITypeHistoryManager.add(CITypeOperateType.DELETE_RELATION, ctr.parent_id,
@ -654,6 +658,7 @@ class CITypeAttributeGroupManager(object):
:param name: :param name:
:param group_order: group order :param group_order: group order
:param attr_order: :param attr_order:
:param is_update:
:return: :return:
""" """
existed = CITypeAttributeGroup.get_by(type_id=type_id, name=name, first=True, to_dict=False) existed = CITypeAttributeGroup.get_by(type_id=type_id, name=name, first=True, to_dict=False)
@ -694,8 +699,8 @@ class CITypeAttributeGroupManager(object):
@staticmethod @staticmethod
def delete(group_id): def delete(group_id):
group = CITypeAttributeGroup.get_by_id(group_id) \ group = (CITypeAttributeGroup.get_by_id(group_id) or
or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id))) abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id))))
group.soft_delete() group.soft_delete()
items = CITypeAttributeGroupItem.get_by(group_id=group_id, to_dict=False) items = CITypeAttributeGroupItem.get_by(group_id=group_id, to_dict=False)
@ -964,8 +969,8 @@ class CITypeTemplateManager(object):
rule['uid'] = current_user.uid rule['uid'] = current_user.uid
try: try:
AutoDiscoveryCITypeCRUD.add(**rule) AutoDiscoveryCITypeCRUD.add(**rule)
except: except Exception as e:
pass current_app.logger.warning("import auto discovery rules failed: {}".format(e))
def import_template(self, tpt): def import_template(self, tpt):
import time import time
@ -1124,8 +1129,8 @@ class CITypeTriggerManager(object):
@staticmethod @staticmethod
def update(_id, notify): def update(_id, notify):
existed = CITypeTrigger.get_by_id(_id) or \ existed = (CITypeTrigger.get_by_id(_id) or
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))) abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
existed2 = existed.to_dict() existed2 = existed.to_dict()
new = existed.update(notify=notify) new = existed.update(notify=notify)
@ -1139,8 +1144,8 @@ class CITypeTriggerManager(object):
@staticmethod @staticmethod
def delete(_id): def delete(_id):
existed = CITypeTrigger.get_by_id(_id) or \ existed = (CITypeTrigger.get_by_id(_id) or
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))) abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
existed.soft_delete() existed.soft_delete()
@ -1163,16 +1168,16 @@ class CITypeTriggerManager(object):
result = [] result = []
for v in values: for v in values:
if isinstance(v.value, (datetime.date, datetime.datetime)) and \ if (isinstance(v.value, (datetime.date, datetime.datetime)) and
(v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d"): (v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d")):
result.append(v) result.append(v)
return result return result
@staticmethod @staticmethod
def trigger_notify(trigger, ci): def trigger_notify(trigger, ci):
if trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or \ if (trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or
not trigger.notify.get('notify_at'): not trigger.notify.get('notify_at')):
from api.tasks.cmdb import trigger_notify from api.tasks.cmdb import trigger_notify
trigger_notify.apply_async(args=(trigger.notify, ci.ci_id), queue=CMDB_QUEUE) trigger_notify.apply_async(args=(trigger.notify, ci.ci_id), queue=CMDB_QUEUE)

View File

@ -99,5 +99,7 @@ CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB" REDIS_PREFIX_CI = "ONE_CMDB"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION" REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
L_TYPE = None L_TYPE = None
L_CI = None L_CI = None

View File

@ -176,8 +176,8 @@ class AttributeHistoryManger(object):
def get_record_detail(record_id): def get_record_detail(record_id):
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
record = OperationRecord.get_by_id(record_id) or \ record = (OperationRecord.get_by_id(record_id) or
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id))) abort(404, ErrFormat.record_not_found.format("id={}".format(record_id))))
username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username
timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S") timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S")

View File

@ -37,10 +37,12 @@ class PreferenceManager(object):
def get_types(instance=False, tree=False): def get_types(instance=False, tree=False):
types = db.session.query(PreferenceShowAttributes.type_id).filter( types = db.session.query(PreferenceShowAttributes.type_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter( PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.deleted.is_(False)).group_by(PreferenceShowAttributes.type_id).all() \ PreferenceShowAttributes.deleted.is_(False)).group_by(
if instance else [] PreferenceShowAttributes.type_id).all() if instance else []
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else [] tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
type_ids = list(set([i.type_id for i in types + tree_types])) type_ids = set([i.type_id for i in types + tree_types])
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids] return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
@staticmethod @staticmethod

View File

@ -24,21 +24,21 @@ class RelationTypeManager(object):
@staticmethod @staticmethod
def add(name): def add(name):
RelationType.get_by(name=name, first=True, to_dict=False) and \ RelationType.get_by(name=name, first=True, to_dict=False) and abort(
abort(400, ErrFormat.relation_type_exists.format(name)) 400, ErrFormat.relation_type_exists.format(name))
return RelationType.create(name=name) return RelationType.create(name=name)
@staticmethod @staticmethod
def update(rel_id, name): def update(rel_id, name):
existed = RelationType.get_by_id(rel_id) or \ existed = RelationType.get_by_id(rel_id) or abort(
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id))) 404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
return existed.update(name=name) return existed.update(name=name)
@staticmethod @staticmethod
def delete(rel_id): def delete(rel_id):
existed = RelationType.get_by_id(rel_id) or \ existed = RelationType.get_by_id(rel_id) or abort(
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id))) 404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
existed.soft_delete() existed.soft_delete()

View File

@ -245,10 +245,8 @@ class Search(object):
new_table = _v_query_sql new_table = _v_query_sql
if self.only_type_query or not self.type_id_list: if self.only_type_query or not self.type_id_list:
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id " \ return ("SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id FROM ({0}) AS C ORDER BY C.value {2} "
"FROM ({0}) AS C " \ "LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count))
"ORDER BY C.value {2} " \
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count)
elif self.type_id_list: elif self.type_id_list:
self.query_sql = """SELECT C.ci_id self.query_sql = """SELECT C.ci_id

View File

@ -297,8 +297,8 @@ class Search(object):
if not attr: if not attr:
raise SearchError(ErrFormat.attribute_not_found.format(field)) raise SearchError(ErrFormat.attribute_not_found.format(field))
sort_by = "{0}.keyword".format(field) \ sort_by = ("{0}.keyword".format(field)
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field)
sorts.append({sort_by: {"order": sort_type}}) sorts.append({sort_by: {"order": sort_type}})
self.query.update(dict(sort=sorts)) self.query.update(dict(sort=sorts))

View File

@ -83,6 +83,7 @@ class AttributeValueManager(object):
def __deserialize_value(value_type, value): def __deserialize_value(value_type, value):
if not value: if not value:
return value return value
deserialize = ValueTypeMap.deserialize[value_type] deserialize = ValueTypeMap.deserialize[value_type]
try: try:
v = deserialize(value) v = deserialize(value)
@ -184,8 +185,8 @@ class AttributeValueManager(object):
return [var for var in schema.get("properties")] return [var for var in schema.get("properties")]
def _compute_attr_value(self, attr, payload, ci): def _compute_attr_value(self, attr, payload, ci):
attrs = self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr') else \ attrs = (self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr')
self._jinja2_parse(attr['compute_script']) else self._jinja2_parse(attr['compute_script']))
not_existed = [i for i in attrs if i not in payload] not_existed = [i for i in attrs if i not in payload]
if ci is not None: if ci is not None:
payload.update(self.get_attr_values(not_existed, ci.id)) payload.update(self.get_attr_values(not_existed, ci.id))

View File

@ -0,0 +1,46 @@
from flask import abort
from api.extensions import db
from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import CommonData
class CommonDataCRUD(object):
@staticmethod
def get_data_by_type(data_type):
return CommonData.get_by(data_type=data_type)
@staticmethod
def get_data_by_id(_id, to_dict=True):
return CommonData.get_by(first=True, id=_id, to_dict=to_dict)
@staticmethod
def create_new_data(data_type, **kwargs):
try:
return CommonData.create(data_type=data_type, **kwargs)
except Exception as e:
db.session.rollback()
abort(400, str(e))
@staticmethod
def update_data(_id, **kwargs):
existed = CommonDataCRUD.get_data_by_id(_id, to_dict=False)
if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id))
try:
return existed.update(**kwargs)
except Exception as e:
db.session.rollback()
abort(400, str(e))
@staticmethod
def delete(_id):
existed = CommonDataCRUD.get_data_by_id(_id, to_dict=False)
if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id))
try:
existed.soft_delete()
except Exception as e:
db.session.rollback()
abort(400, str(e))

View File

@ -54,3 +54,4 @@ class ErrFormat(CommonErrFormat):
email_is_required = "邮箱不能为空" email_is_required = "邮箱不能为空"
email_format_error = "邮箱格式错误" email_format_error = "邮箱格式错误"
common_data_not_found = "ID {} 找不到记录"

View File

@ -4,8 +4,7 @@ from api.lib.common_setting.utils import get_cur_time_str
def allowed_file(filename, allowed_extensions): def allowed_file(filename, allowed_extensions):
return '.' in filename and \ return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
filename.rsplit('.', 1)[1].lower() in allowed_extensions
def generate_new_file_name(name): def generate_new_file_name(name):
@ -13,4 +12,5 @@ def generate_new_file_name(name):
prev_name = ''.join(name.split(f".{ext}")[:-1]) prev_name = ''.join(name.split(f".{ext}")[:-1])
uid = str(uuid.uuid4()) uid = str(uuid.uuid4())
cur_str = get_cur_time_str('_') cur_str = get_cur_time_str('_')
return f"{prev_name}_{cur_str}_{uid}.{ext}" return f"{prev_name}_{cur_str}_{uid}.{ext}"

View File

@ -55,8 +55,8 @@ def args_validate(model_cls, exclude_args=None):
if exclude_args and arg in exclude_args: if exclude_args and arg in exclude_args:
continue continue
if attr.type.python_type == str and attr.type.length and \ if attr.type.python_type == str and attr.type.length and (
len(request.values[arg] or '') > attr.type.length: len(request.values[arg] or '') > attr.type.length):
return abort(400, CommonErrFormat.argument_str_length_limit.format(arg, attr.type.length)) return abort(400, CommonErrFormat.argument_str_length_limit.format(arg, attr.type.length))
elif attr.type.python_type in (int, float) and request.values[arg]: elif attr.type.python_type in (int, float) and request.values[arg]:

View File

@ -5,8 +5,10 @@ import hashlib
import requests import requests
import six import six
from flask import abort, session from flask import abort
from flask import current_app, request from flask import current_app
from flask import request
from flask import session
from flask_login import current_user from flask_login import current_user
from api.extensions import cache from api.extensions import cache
@ -85,8 +87,8 @@ class ACLManager(object):
if user: if user:
return Role.get_by(name=name, uid=user.uid, first=True, to_dict=False) return Role.get_by(name=name, uid=user.uid, first=True, to_dict=False)
return Role.get_by(name=name, app_id=self.app_id, first=True, to_dict=False) or \ return (Role.get_by(name=name, app_id=self.app_id, first=True, to_dict=False) or
Role.get_by(name=name, first=True, to_dict=False) Role.get_by(name=name, first=True, to_dict=False))
def add_resource(self, name, resource_type_name=None): def add_resource(self, name, resource_type_name=None):
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False) resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)

View File

@ -8,7 +8,9 @@ from flask import abort
from flask import current_app from flask import current_app
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.audit import AuditScope
from api.lib.perm.acl.resp_format import ErrFormat from api.lib.perm.acl.resp_format import ErrFormat
from api.models.acl import App from api.models.acl import App

View File

@ -9,8 +9,16 @@ from flask_login import current_user
from sqlalchemy import func from sqlalchemy import func
from api.lib.perm.acl import AppCache from api.lib.perm.acl import AppCache
from api.models.acl import AuditPermissionLog, AuditResourceLog, AuditRoleLog, AuditTriggerLog, Permission, Resource, \ from api.models.acl import AuditPermissionLog
ResourceGroup, ResourceType, Role, RolePermission from api.models.acl import AuditResourceLog
from api.models.acl import AuditRoleLog
from api.models.acl import AuditTriggerLog
from api.models.acl import Permission
from api.models.acl import Resource
from api.models.acl import ResourceGroup
from api.models.acl import ResourceType
from api.models.acl import Role
from api.models.acl import RolePermission
class AuditScope(str, Enum): class AuditScope(str, Enum):
@ -91,11 +99,8 @@ class AuditCRUD(object):
criterion.append(AuditPermissionLog.operate_type == v) criterion.append(AuditPermissionLog.operate_type == v)
records = AuditPermissionLog.query.filter( records = AuditPermissionLog.query.filter(
AuditPermissionLog.deleted == 0, AuditPermissionLog.deleted == 0, *criterion).order_by(
*criterion) \ AuditPermissionLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
.order_by(AuditPermissionLog.id.desc()) \
.offset((page - 1) * page_size) \
.limit(page_size).all()
data = { data = {
'data': [r.to_dict() for r in records], 'data': [r.to_dict() for r in records],
@ -158,10 +163,8 @@ class AuditCRUD(object):
elif k == 'operate_type': elif k == 'operate_type':
criterion.append(AuditRoleLog.operate_type == v) criterion.append(AuditRoleLog.operate_type == v)
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion) \ records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion).order_by(
.order_by(AuditRoleLog.id.desc()) \ AuditRoleLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
.offset((page - 1) * page_size) \
.limit(page_size).all()
data = { data = {
'data': [r.to_dict() for r in records], 'data': [r.to_dict() for r in records],
@ -223,11 +226,8 @@ class AuditCRUD(object):
criterion.append(AuditResourceLog.operate_type == v) criterion.append(AuditResourceLog.operate_type == v)
records = AuditResourceLog.query.filter( records = AuditResourceLog.query.filter(
AuditResourceLog.deleted == 0, AuditResourceLog.deleted == 0, *criterion).order_by(
*criterion) \ AuditResourceLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
.order_by(AuditResourceLog.id.desc()) \
.offset((page - 1) * page_size) \
.limit(page_size).all()
data = { data = {
'data': [r.to_dict() for r in records], 'data': [r.to_dict() for r in records],
@ -257,11 +257,8 @@ class AuditCRUD(object):
criterion.append(AuditTriggerLog.operate_type == v) criterion.append(AuditTriggerLog.operate_type == v)
records = AuditTriggerLog.query.filter( records = AuditTriggerLog.query.filter(
AuditTriggerLog.deleted == 0, AuditTriggerLog.deleted == 0, *criterion).order_by(
*criterion) \ AuditTriggerLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
.order_by(AuditTriggerLog.id.desc()) \
.offset((page - 1) * page_size) \
.limit(page_size).all()
data = { data = {
'data': [r.to_dict() for r in records], 'data': [r.to_dict() for r in records],

View File

@ -60,15 +60,15 @@ class UserCache(object):
@classmethod @classmethod
def get(cls, key): def get(cls, key):
user = cache.get(cls.PREFIX_ID.format(key)) or \ user = (cache.get(cls.PREFIX_ID.format(key)) or
cache.get(cls.PREFIX_NAME.format(key)) or \ cache.get(cls.PREFIX_NAME.format(key)) or
cache.get(cls.PREFIX_NICK.format(key)) or \ cache.get(cls.PREFIX_NICK.format(key)) or
cache.get(cls.PREFIX_WXID.format(key)) cache.get(cls.PREFIX_WXID.format(key)))
if not user: if not user:
user = User.query.get(key) or \ user = (User.query.get(key) or
User.query.get_by_username(key) or \ User.query.get_by_username(key) or
User.query.get_by_nickname(key) or \ User.query.get_by_nickname(key) or
User.query.get_by_wxid(key) User.query.get_by_wxid(key))
if user: if user:
cls.set(user) cls.set(user)

View File

@ -4,7 +4,9 @@ import datetime
from flask import abort from flask import abort
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditOperateSource from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateSource
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.cache import PermissionCache from api.lib.perm.acl.cache import PermissionCache
from api.lib.perm.acl.cache import RoleCache from api.lib.perm.acl.cache import RoleCache
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
@ -97,8 +99,8 @@ class PermissionCRUD(object):
elif group_id is not None: elif group_id is not None:
from api.models.acl import ResourceGroup from api.models.acl import ResourceGroup
group = ResourceGroup.get_by_id(group_id) or \ group = ResourceGroup.get_by_id(group_id) or abort(
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id))) 404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
app_id = group.app_id app_id = group.app_id
rt_id = group.resource_type_id rt_id = group.resource_type_id
if not perms: if not perms:
@ -206,8 +208,8 @@ class PermissionCRUD(object):
if resource_id is not None: if resource_id is not None:
from api.models.acl import Resource from api.models.acl import Resource
resource = Resource.get_by_id(resource_id) or \ resource = Resource.get_by_id(resource_id) or abort(
abort(404, ErrFormat.resource_not_found.format("id={}".format(resource_id))) 404, ErrFormat.resource_not_found.format("id={}".format(resource_id)))
app_id = resource.app_id app_id = resource.app_id
rt_id = resource.resource_type_id rt_id = resource.resource_type_id
if not perms: if not perms:
@ -216,8 +218,8 @@ class PermissionCRUD(object):
elif group_id is not None: elif group_id is not None:
from api.models.acl import ResourceGroup from api.models.acl import ResourceGroup
group = ResourceGroup.get_by_id(group_id) or \ group = ResourceGroup.get_by_id(group_id) or abort(
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id))) 404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
app_id = group.app_id app_id = group.app_id
rt_id = group.resource_type_id rt_id = group.resource_type_id

View File

@ -5,7 +5,9 @@ from flask import abort
from flask import current_app from flask import current_app
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.audit import AuditScope
from api.lib.perm.acl.cache import ResourceCache from api.lib.perm.acl.cache import ResourceCache
from api.lib.perm.acl.cache import ResourceGroupCache from api.lib.perm.acl.cache import ResourceGroupCache
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
@ -102,8 +104,8 @@ class ResourceTypeCRUD(object):
@classmethod @classmethod
def delete(cls, rt_id): def delete(cls, rt_id):
rt = ResourceType.get_by_id(rt_id) or \ rt = ResourceType.get_by_id(rt_id) or abort(
abort(404, ErrFormat.resource_type_not_found.format("id={}".format(rt_id))) 404, ErrFormat.resource_type_not_found.format("id={}".format(rt_id)))
Resource.get_by(resource_type_id=rt_id) and abort(400, ErrFormat.resource_type_cannot_delete) Resource.get_by(resource_type_id=rt_id) and abort(400, ErrFormat.resource_type_cannot_delete)
@ -165,8 +167,8 @@ class ResourceGroupCRUD(object):
@staticmethod @staticmethod
def add(name, type_id, app_id, uid=None): def add(name, type_id, app_id, uid=None):
ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and \ ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
abort(400, ErrFormat.resource_group_exists.format(name)) 400, ErrFormat.resource_group_exists.format(name))
rg = ResourceGroup.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid) rg = ResourceGroup.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
AuditCRUD.add_resource_log(app_id, AuditOperateType.create, AuditCRUD.add_resource_log(app_id, AuditOperateType.create,
@ -175,8 +177,8 @@ class ResourceGroupCRUD(object):
@staticmethod @staticmethod
def update(rg_id, items): def update(rg_id, items):
rg = ResourceGroup.get_by_id(rg_id) or \ rg = ResourceGroup.get_by_id(rg_id) or abort(
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id))) 404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
existed = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False) existed = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False)
existed_ids = [i.resource_id for i in existed] existed_ids = [i.resource_id for i in existed]
@ -196,8 +198,8 @@ class ResourceGroupCRUD(object):
@staticmethod @staticmethod
def delete(rg_id): def delete(rg_id):
rg = ResourceGroup.get_by_id(rg_id) or \ rg = ResourceGroup.get_by_id(rg_id) or abort(
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id))) 404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
origin = rg.to_dict() origin = rg.to_dict()
rg.soft_delete() rg.soft_delete()
@ -266,8 +268,8 @@ class ResourceCRUD(object):
def add(cls, name, type_id, app_id, uid=None): def add(cls, name, type_id, app_id, uid=None):
type_id = cls._parse_resource_type_id(type_id, app_id) type_id = cls._parse_resource_type_id(type_id, app_id)
Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) and \ Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
abort(400, ErrFormat.resource_exists.format(name)) 400, ErrFormat.resource_exists.format(name))
r = Resource.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid) r = Resource.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)

View File

@ -10,7 +10,9 @@ from sqlalchemy import or_
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.app import AppCRUD from api.lib.perm.acl.app import AppCRUD
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.audit import AuditScope
from api.lib.perm.acl.cache import AppCache from api.lib.perm.acl.cache import AppCache
from api.lib.perm.acl.cache import HasResourceRoleCache from api.lib.perm.acl.cache import HasResourceRoleCache
from api.lib.perm.acl.cache import RoleCache from api.lib.perm.acl.cache import RoleCache
@ -69,16 +71,16 @@ class RoleRelationCRUD(object):
@staticmethod @staticmethod
def get_parent_ids(rid, app_id): def get_parent_ids(rid, app_id):
if app_id is not None: if app_id is not None:
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)] + \ return ([i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)] +
[i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=None, to_dict=False)] [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=None, to_dict=False)])
else: else:
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)] return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)]
@staticmethod @staticmethod
def get_child_ids(rid, app_id): def get_child_ids(rid, app_id):
if app_id is not None: if app_id is not None:
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)] + \ return ([i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)] +
[i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=None, to_dict=False)] [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=None, to_dict=False)])
else: else:
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)] return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)]

View File

@ -6,9 +6,10 @@ import json
import re import re
from fnmatch import fnmatch from fnmatch import fnmatch
from flask import abort, current_app from flask import abort
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.const import ACL_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.resp_format import ErrFormat from api.lib.perm.acl.resp_format import ErrFormat

View File

@ -9,7 +9,9 @@ from flask import abort
from flask_login import current_user from flask_login import current_user
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.audit import AuditScope
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.resp_format import ErrFormat from api.lib.perm.acl.resp_format import ErrFormat
from api.lib.perm.acl.role import RoleCRUD from api.lib.perm.acl.role import RoleCRUD
@ -49,11 +51,9 @@ class UserCRUD(object):
kwargs['block'] = 0 kwargs['block'] = 0
kwargs['key'], kwargs['secret'] = cls.gen_key_secret() kwargs['key'], kwargs['secret'] = cls.gen_key_secret()
user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by( user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by(User.employee_id.desc()).first()
User.employee_id.desc()).first()
biggest_employee_id = int(float(user_employee.employee_id)) \ biggest_employee_id = int(float(user_employee.employee_id)) if user_employee is not None else 0
if user_employee is not None else 0
kwargs['employee_id'] = '{0:04d}'.format(biggest_employee_id + 1) kwargs['employee_id'] = '{0:04d}'.format(biggest_employee_id + 1)
user = User.create(**kwargs) user = User.create(**kwargs)

View File

@ -1,7 +1,6 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import base64 import base64
import json
import sys import sys
import time import time
from typing import Set from typing import Set
@ -113,7 +112,7 @@ class RedisHandler(object):
try: try:
ret = self.r.hdel(prefix, key_id) ret = self.r.hdel(prefix, key_id)
if not ret: if not ret:
current_app.logger.warn("[{0}] is not in redis".format(key_id)) current_app.logger.warning("[{0}] is not in redis".format(key_id))
except Exception as e: except Exception as e:
current_app.logger.error("delete redis key error, {0}".format(str(e))) current_app.logger.error("delete redis key error, {0}".format(str(e)))
@ -204,9 +203,9 @@ class ESHandler(object):
res = self.es.search(index=self.index, body=query, filter_path=filter_path) res = self.es.search(index=self.index, body=query, filter_path=filter_path)
if res['hits'].get('hits'): if res['hits'].get('hits'):
return res['hits']['total']['value'], \ return (res['hits']['total']['value'],
[i['_source'] for i in res['hits']['hits']], \ [i['_source'] for i in res['hits']['hits']],
res.get("aggregations", {}) res.get("aggregations", {}))
else: else:
return 0, [], {} return 0, [], {}
@ -257,93 +256,10 @@ class Lock(object):
self.release() self.release()
class Redis2Handler(object):
def __init__(self, flask_app=None, prefix=None):
self.flask_app = flask_app
self.prefix = prefix
self.r = None
def init_app(self, app):
self.flask_app = app
config = self.flask_app.config
try:
pool = redis.ConnectionPool(
max_connections=config.get("REDIS_MAX_CONN"),
host=config.get("ONEAGENT_REDIS_HOST"),
port=config.get("ONEAGENT_REDIS_PORT"),
db=config.get("ONEAGENT_REDIS_DB"),
password=config.get("ONEAGENT_REDIS_PASSWORD")
)
self.r = redis.Redis(connection_pool=pool)
except Exception as e:
current_app.logger.warning(str(e))
current_app.logger.error("init redis connection failed")
def get(self, key):
try:
value = json.loads(self.r.get(key))
except:
return
return value
def lrange(self, key, start=0, end=-1):
try:
value = "".join(map(redis_decode, self.r.lrange(key, start, end) or []))
except:
return
return value
def lrange2(self, key, start=0, end=-1):
try:
return list(map(redis_decode, self.r.lrange(key, start, end) or []))
except:
return []
def llen(self, key):
try:
return self.r.llen(key) or 0
except:
return 0
def hget(self, key, field):
try:
return self.r.hget(key, field)
except Exception as e:
current_app.logger.warning("hget redis failed, %s" % str(e))
return
def hset(self, key, field, value):
try:
self.r.hset(key, field, value)
except Exception as e:
current_app.logger.warning("hset redis failed, %s" % str(e))
return
def expire(self, key, timeout):
try:
self.r.expire(key, timeout)
except Exception as e:
current_app.logger.warning("expire redis failed, %s" % str(e))
return
def redis_decode(x):
try:
return x.decode()
except Exception as e:
print(x, e)
try:
return x.decode("gb18030")
except:
return "decode failed"
class AESCrypto(object): class AESCrypto(object):
BLOCK_SIZE = 16 # Bytes BLOCK_SIZE = 16 # Bytes
pad = lambda s: s + (AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) * \ pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
chr(AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) chr(AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE))
unpad = lambda s: s[:-ord(s[len(s) - 1:])] unpad = lambda s: s[:-ord(s[len(s) - 1:])]
iv = '0102030405060708' iv = '0102030405060708'
@ -352,7 +268,7 @@ class AESCrypto(object):
def key(): def key():
key = current_app.config.get("SECRET_KEY")[:16] key = current_app.config.get("SECRET_KEY")[:16]
if len(key) < 16: if len(key) < 16:
key = "{}{}".format(key, (16 - len(key) * "x")) key = "{}{}".format(key, (16 - len(key)) * "x")
return key.encode('utf8') return key.encode('utf8')

View File

@ -80,3 +80,10 @@ class InternalMessage(Model):
category = db.Column(db.VARCHAR(128), nullable=False) category = db.Column(db.VARCHAR(128), nullable=False)
message_data = db.Column(db.JSON, nullable=True) message_data = db.Column(db.JSON, nullable=True)
employee_id = db.Column(db.Integer, db.ForeignKey('common_employee.employee_id'), comment='ID') employee_id = db.Column(db.Integer, db.ForeignKey('common_employee.employee_id'), comment='ID')
class CommonData(Model):
__table_name__ = 'common_data'
data_type = db.Column(db.VARCHAR(255), default='')
data = db.Column(db.JSON)

View File

@ -2,7 +2,8 @@
import os import os
import sys import sys
from inspect import getmembers, isclass from inspect import getmembers
from inspect import isclass
import six import six
from flask import jsonify from flask import jsonify

View File

@ -5,17 +5,20 @@ import re
from celery_once import QueueOnce from celery_once import QueueOnce
from flask import current_app from flask import current_app
from werkzeug.exceptions import BadRequest, NotFound from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import NotFound
from api.extensions import celery from api.extensions import celery
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.audit import AuditOperateSource
from api.lib.perm.acl.audit import AuditOperateType
from api.lib.perm.acl.cache import AppCache from api.lib.perm.acl.cache import AppCache
from api.lib.perm.acl.cache import RoleCache from api.lib.perm.acl.cache import RoleCache
from api.lib.perm.acl.cache import RoleRelationCache from api.lib.perm.acl.cache import RoleRelationCache
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.const import ACL_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.record import OperateRecordCRUD from api.lib.perm.acl.record import OperateRecordCRUD
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditOperateSource
from api.models.acl import Resource from api.models.acl import Resource
from api.models.acl import Role from api.models.acl import Role
from api.models.acl import Trigger from api.models.acl import Trigger

View File

@ -2,12 +2,13 @@
import datetime import datetime
import six
import jwt import jwt
import six
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask_login import login_user, logout_user from flask_login import login_user
from flask_login import logout_user
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.perm.acl.cache import User from api.lib.perm.acl.cache import User

View File

@ -11,7 +11,8 @@ from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.const import ExistPolicy from api.lib.cmdb.const import ExistPolicy
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RetKey from api.lib.cmdb.const import RetKey
from api.lib.cmdb.perms import has_perm_for_ci from api.lib.cmdb.perms import has_perm_for_ci
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError

View File

@ -6,7 +6,9 @@ from flask import request
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.ci_type import CITypeRelationManager from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager

View File

@ -7,7 +7,8 @@ from flask import abort
from flask import request from flask import request
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.history import AttributeHistoryManger from api.lib.cmdb.history import AttributeHistoryManger
from api.lib.cmdb.history import CITypeHistoryManager from api.lib.cmdb.history import CITypeHistoryManager

View File

@ -5,7 +5,9 @@ from flask import abort
from flask import request from flask import request
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat

View File

@ -0,0 +1,35 @@
from flask import request
from api.lib.common_setting.common_data import CommonDataCRUD
from api.resource import APIView
prefix = '/data'
class DataView(APIView):
url_prefix = (f'{prefix}/<string:data_type>',)
def get(self, data_type):
data_list = CommonDataCRUD.get_data_by_type(data_type)
return self.jsonify(data_list)
def post(self, data_type):
params = request.json
CommonDataCRUD.create_new_data(data_type, **params)
return self.jsonify(params)
class DataViewWithId(APIView):
url_prefix = (f'{prefix}/<string:data_type>/<int:_id>',)
def put(self, data_type, _id):
params = request.json
res = CommonDataCRUD.update_data(_id, **params)
return self.jsonify(res.to_dict())
def delete(self, data_type, _id):
CommonDataCRUD.delete(_id)
return self.jsonify({})

View File

@ -6,7 +6,9 @@ from flask import Blueprint
from flask_restful import Api from flask_restful import Api
from api.resource import register_resources from api.resource import register_resources
from .account import LoginView, LogoutView, AuthWithKeyView from .account import AuthWithKeyView
from .account import LoginView
from .account import LogoutView
HERE = os.path.abspath(os.path.dirname(__file__)) HERE = os.path.abspath(os.path.dirname(__file__))