Compare commits

...

16 Commits

Author SHA1 Message Date
pycook
ab8ccf7d1b ui: lint 2024-01-12 18:21:47 +08:00
wang-liang0615
ae1f0f6b4f lint regexSelect (#382) 2024-01-12 18:09:03 +08:00
wang-liang0615
ff47e0ade6 feat:citype regex check & pref:edit is_list (#380) 2024-01-12 17:09:44 +08:00
pycook
0dd272fb04 feat(api): password supports regular check 2024-01-12 16:56:10 +08:00
pycook
6bc5c1516d feat(api): Attributes support regular check (#379)
feat(api): Attributes support regular check
2024-01-12 13:05:37 +08:00
wang-liang0615
4a4b9e6ef0 feat(cmdb-ui):preference citype order (#378) 2024-01-12 11:14:53 +08:00
pycook
6e3f9478b3 Dev api 240111 (#377)
* feat(api): My subscription supports CIType sorting

* feat(api): db change
2024-01-11 18:01:37 +08:00
pycook
691051c254 perf(api): /api/v0.1/ci/adc/statistics
perf(api): /api/v0.1/ci/adc/statistics
2024-01-11 10:10:01 +08:00
pycook
8f066e95a6 fix(api): grant by attr (#373) 2024-01-10 16:52:27 +08:00
pycook
75bca39bf6 fix(api): commands add-user 2024-01-10 11:56:39 +08:00
pycook
3360e4d0fe feat(db): set variable sql_mode
feat(db): set variable sql_mode
2024-01-10 10:28:15 +08:00
wang-liang0615
521fcd0ba2 fix(ui):some fix (#370)
* pref(ui):some bugfix & some style

* fix(ui):some fix
2024-01-10 09:46:02 +08:00
wang-liang0615
fc113425cb pref(ui):some bugfix & some style (#369) 2024-01-09 14:48:13 +08:00
pycook
81a76a9632 fix(api): cmdb-init-acl commands (#368) 2024-01-09 10:04:53 +08:00
wang-liang0615
9ec105ca37 fix(ui):logout (#365) 2024-01-04 16:12:20 +08:00
wang-liang0615
1e1c92a3ef refactor(ui):extract pager components (#364) 2024-01-04 13:41:07 +08:00
68 changed files with 1245 additions and 1008 deletions

View File

@@ -5,8 +5,8 @@ name = "pypi"
[packages] [packages]
# Flask # Flask
Flask = "==2.3.2" Flask = "==2.2.5"
Werkzeug = ">=2.3.6" Werkzeug = "==2.2.3"
click = ">=5.0" click = ">=5.0"
# Api # Api
Flask-RESTful = "==0.3.10" Flask-RESTful = "==0.3.10"

View File

@@ -50,7 +50,7 @@ def add_user():
if is_admin: if is_admin:
app = AppCache.get('acl') or App.create(name='acl') app = AppCache.get('acl') or App.create(name='acl')
acl_admin = RoleCache.get('acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True) acl_admin = RoleCache.get_by_name(app.id, 'acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
rid = RoleCache.get_by_name(None, username).id rid = RoleCache.get_by_name(None, username).id
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id) RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)

View File

@@ -1,14 +1,13 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import click
import copy import copy
import datetime import datetime
import json import json
import requests
import time import time
import uuid import uuid
import click
import requests
from flask import current_app from flask import current_app
from flask.cli import with_appcontext from flask.cli import with_appcontext
from flask_login import login_user from flask_login import login_user
@@ -115,6 +114,8 @@ def cmdb_init_acl():
_app = AppCache.get('cmdb') or App.create(name='cmdb') _app = AppCache.get('cmdb') or App.create(name='cmdb')
app_id = _app.id app_id = _app.id
current_app.test_request_context().push()
# 1. add resource type # 1. add resource type
for resource_type in ResourceTypeEnum.all(): for resource_type in ResourceTypeEnum.all():
try: try:
@@ -183,11 +184,19 @@ def cmdb_counter():
UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com') UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com')
login_user(UserCache.get('worker')) login_user(UserCache.get('worker'))
i = 0
while True: while True:
try: try:
db.session.remove() db.session.remove()
CMDBCounterCache.reset() CMDBCounterCache.reset()
if i % 5 == 0:
CMDBCounterCache.flush_adc_counter()
i = 0
i += 1
except: except:
import traceback import traceback
print(traceback.format_exc()) print(traceback.format_exc())

View File

@@ -89,6 +89,16 @@ def db_setup():
""" """
db.create_all() db.create_all()
db.session.execute("set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,"
"ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'")
db.session.commit()
try:
db.session.execute("set global tidb_enable_noop_functions='ON'")
db.session.commit()
except:
pass
@click.group() @click.group()
def translate(): def translate():

View File

@@ -5,10 +5,14 @@ from __future__ import unicode_literals
from flask import current_app from flask import current_app
from api.extensions import cache from api.extensions import cache
from api.extensions import db
from api.lib.cmdb.custom_dashboard import CustomDashboardManager from api.lib.cmdb.custom_dashboard import CustomDashboardManager
from api.models.cmdb import Attribute from api.models.cmdb import Attribute
from api.models.cmdb import CI
from api.models.cmdb import CIType from api.models.cmdb import CIType
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
from api.models.cmdb import PreferenceShowAttributes
from api.models.cmdb import PreferenceTreeView
from api.models.cmdb import RelationType from api.models.cmdb import RelationType
@@ -226,7 +230,9 @@ class CITypeAttributeCache(object):
class CMDBCounterCache(object): class CMDBCounterCache(object):
KEY = 'CMDB::Counter' KEY = 'CMDB::Counter::dashboard'
KEY2 = 'CMDB::Counter::adc'
KEY3 = 'CMDB::Counter::sub'
@classmethod @classmethod
def get(cls): def get(cls):
@@ -429,3 +435,47 @@ class CMDBCounterCache(object):
return return
return numfound return numfound
@classmethod
def flush_adc_counter(cls):
res = db.session.query(CI.type_id, CI.is_auto_discovery)
result = dict()
for i in res:
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
result[i.type_id]['total'] += 1
if i.is_auto_discovery:
result[i.type_id]['auto_discovery'] += 1
cache.set(cls.KEY2, result, timeout=0)
return result
@classmethod
def get_adc_counter(cls):
return cache.get(cls.KEY2) or cls.flush_adc_counter()
@classmethod
def flush_sub_counter(cls):
result = dict(type_id2users=dict())
types = db.session.query(PreferenceShowAttributes.type_id,
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
PreferenceShowAttributes.deleted.is_(False)).group_by(
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
for i in types:
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
types = PreferenceTreeView.get_by(to_dict=False)
for i in types:
result['type_id2users'].setdefault(i.type_id, [])
if i.uid not in result['type_id2users'][i.type_id]:
result['type_id2users'][i.type_id].append(i.uid)
cache.set(cls.KEY3, result, timeout=0)
return result
@classmethod
def get_sub_counter(cls):
return cache.get(cls.KEY3) or cls.flush_sub_counter()

View File

@@ -5,7 +5,6 @@ import copy
import datetime import datetime
import json import json
import threading import threading
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask_login import current_user from flask_login import current_user
@@ -16,6 +15,7 @@ from api.extensions import rd
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.cache import CMDBCounterCache
from api.lib.cmdb.ci_type import CITypeAttributeManager from api.lib.cmdb.ci_type import CITypeAttributeManager
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
@@ -218,15 +218,7 @@ class CIManager(object):
@classmethod @classmethod
def get_ad_statistics(cls): def get_ad_statistics(cls):
res = CI.get_by(to_dict=False) return CMDBCounterCache.get_adc_counter()
result = dict()
for i in res:
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
result[i.type_id]['total'] += 1
if i.is_auto_discovery:
result[i.type_id]['auto_discovery'] += 1
return result
@staticmethod @staticmethod
def ci_is_exist(unique_key, unique_value, type_id): def ci_is_exist(unique_key, unique_value, type_id):
@@ -368,6 +360,8 @@ class CIManager(object):
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT: if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
ci_dict[attr.name] = now ci_dict[attr.name] = now
value_manager = AttributeValueManager()
computed_attrs = [] computed_attrs = []
for _, attr in attrs: for _, attr in attrs:
if attr.is_computed: if attr.is_computed:
@@ -378,7 +372,8 @@ class CIManager(object):
elif attr.alias in ci_dict: elif attr.alias in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.alias) password_dict[attr.id] = ci_dict.pop(attr.alias)
value_manager = AttributeValueManager() if attr.re_check and password_dict.get(attr.id):
value_manager.check_re(attr.re_check, password_dict[attr.id])
if computed_attrs: if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci) value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
@@ -437,6 +432,8 @@ class CIManager(object):
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT: if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
ci_dict[attr.name] = now ci_dict[attr.name] = now
value_manager = AttributeValueManager()
password_dict = dict() password_dict = dict()
computed_attrs = list() computed_attrs = list()
for _, attr in attrs: for _, attr in attrs:
@@ -448,7 +445,8 @@ class CIManager(object):
elif attr.alias in ci_dict: elif attr.alias in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.alias) password_dict[attr.id] = ci_dict.pop(attr.alias)
value_manager = AttributeValueManager() if attr.re_check and password_dict.get(attr.id):
value_manager.check_re(attr.re_check, password_dict[attr.id])
if computed_attrs: if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci) value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)

View File

@@ -1,7 +1,6 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import copy import copy
import toposort import toposort
from flask import abort from flask import abort
from flask import current_app from flask import current_app
@@ -46,6 +45,7 @@ from api.models.cmdb import CITypeRelation
from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeTrigger
from api.models.cmdb import CITypeUniqueConstraint from api.models.cmdb import CITypeUniqueConstraint
from api.models.cmdb import CustomDashboard from api.models.cmdb import CustomDashboard
from api.models.cmdb import PreferenceCITypeOrder
from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceShowAttributes
@@ -75,12 +75,13 @@ class CITypeManager(object):
return CIType.get_by_id(ci_type.id) return CIType.get_by_id(ci_type.id)
@staticmethod @staticmethod
def get_ci_types(type_name=None): def get_ci_types(type_name=None, like=True):
resources = None resources = None
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'): if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)]) resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
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) if like else CIType.get_by(name=type_name))
res = list() res = list()
for type_dict in ci_types: for type_dict in ci_types:
attr = AttributeCache.get(type_dict["unique_id"]) attr = AttributeCache.get(type_dict["unique_id"])
@@ -222,7 +223,7 @@ class CITypeManager(object):
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard, for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger, CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger,
AutoDiscoveryCIType, CIFilterPerms]: AutoDiscoveryCIType, CIFilterPerms, PreferenceCITypeOrder]:
for item in table.get_by(type_id=type_id, to_dict=False): for item in table.get_by(type_id=type_id, to_dict=False):
item.soft_delete(commit=False) item.soft_delete(commit=False)
@@ -1274,7 +1275,7 @@ class CITypeTemplateManager(object):
from api.lib.common_setting.upload_file import CommonFileCRUD from api.lib.common_setting.upload_file import CommonFileCRUD
tpt = dict( tpt = dict(
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name), ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False),
ci_type_auto_discovery_rules=list(), ci_type_auto_discovery_rules=list(),
type2attributes=dict(), type2attributes=dict(),
type2attribute_group=dict(), type2attribute_group=dict(),

View File

@@ -114,6 +114,8 @@ class CIFilterPermsCRUD(DBMixin):
obj.soft_delete() obj.soft_delete()
return obj
else: else:
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'): if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'):
return return

View File

@@ -2,7 +2,6 @@
import copy import copy
import six import six
import toposort import toposort
from flask import abort from flask import abort
@@ -14,6 +13,7 @@ from api.lib.cmdb.attribute import AttributeManager
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.cache import CMDBCounterCache
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
@@ -24,6 +24,7 @@ from api.lib.exception import AbortException
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import PreferenceCITypeOrder
from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceShowAttributes
@@ -38,13 +39,22 @@ class PreferenceManager(object):
@staticmethod @staticmethod
def get_types(instance=False, tree=False): def get_types(instance=False, tree=False):
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
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.deleted.is_(False)).group_by(
PreferenceShowAttributes.type_id).all() if instance else [] PreferenceShowAttributes.type_id).all() if instance else []
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
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 = set([i.type_id for i in types + tree_types]) tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
ci_type_order) if i.is_tree}.get(x.type_id, 1))
type_ids = [i.type_id for i in types + tree_types]
if types and tree_types:
type_ids = set(type_ids)
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]
@@ -59,32 +69,36 @@ class PreferenceManager(object):
:param tree: :param tree:
:return: :return:
""" """
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()), result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()))
type_id2users=dict())
result.update(CMDBCounterCache.get_sub_counter())
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
if instance: if instance:
types = db.session.query(PreferenceShowAttributes.type_id, types = db.session.query(PreferenceShowAttributes.type_id,
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter( PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
PreferenceShowAttributes.deleted.is_(False)).group_by( PreferenceShowAttributes.deleted.is_(False)).filter(
PreferenceShowAttributes.uid == current_user.uid).group_by(
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id) PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
for i in types: for i in types:
if i.uid == current_user.uid: result['self']['instance'].append(i.type_id)
result['self']['instance'].append(i.type_id) if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): result['self']['type_id2subs_time'][i.type_id] = i.created_at
result['self']['type_id2subs_time'][i.type_id] = i.created_at
result['type_id2users'].setdefault(i.type_id, []).append(i.uid) instance_order = [i.type_id for i in ci_type_order if not i.is_tree]
if len(instance_order) == len(result['self']['instance']):
result['self']['instance'] = instance_order
if tree: if tree:
types = PreferenceTreeView.get_by(to_dict=False) types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False)
for i in types: for i in types:
if i.uid == current_user.uid: result['self']['tree'].append(i.type_id)
result['self']['tree'].append(i.type_id) if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): result['self']['type_id2subs_time'][i.type_id] = i.created_at
result['self']['type_id2subs_time'][i.type_id] = i.created_at
result['type_id2users'].setdefault(i.type_id, []) tree_order = [i.type_id for i in ci_type_order if i.is_tree]
if i.uid not in result['type_id2users'][i.type_id]: if len(tree_order) == len(result['self']['tree']):
result['type_id2users'][i.type_id].append(i.uid) result['self']['tree'] = tree_order
return result return result
@@ -151,9 +165,22 @@ class PreferenceManager(object):
if i.attr_id not in attr_dict: if i.attr_id not in attr_dict:
i.soft_delete() i.soft_delete()
if not existed_all and attr_order:
cls.add_ci_type_order_item(type_id, is_tree=False)
elif not PreferenceShowAttributes.get_by(type_id=type_id, uid=current_user.uid, to_dict=False):
cls.delete_ci_type_order_item(type_id, is_tree=False)
@staticmethod @staticmethod
def get_tree_view(): def get_tree_view():
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, is_tree=True, to_dict=False),
key=lambda x: x.order)
res = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=True) res = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=True)
if ci_type_order:
res = sorted(res, key=lambda x: {ii.type_id: idx for idx, ii in enumerate(
ci_type_order)}.get(x['type_id'], 1))
for item in res: for item in res:
if item["levels"]: if item["levels"]:
ci_type = CITypeCache.get(item['type_id']).to_dict() ci_type = CITypeCache.get(item['type_id']).to_dict()
@@ -172,8 +199,8 @@ class PreferenceManager(object):
return res return res
@staticmethod @classmethod
def create_or_update_tree_view(type_id, levels): def create_or_update_tree_view(cls, type_id, levels):
attrs = CITypeAttributesCache.get(type_id) attrs = CITypeAttributesCache.get(type_id)
for idx, i in enumerate(levels): for idx, i in enumerate(levels):
for attr in attrs: for attr in attrs:
@@ -185,9 +212,12 @@ class PreferenceManager(object):
if existed is not None: if existed is not None:
if not levels: if not levels:
existed.soft_delete() existed.soft_delete()
cls.delete_ci_type_order_item(type_id, is_tree=True)
return existed return existed
return existed.update(levels=levels) return existed.update(levels=levels)
elif levels: elif levels:
cls.add_ci_type_order_item(type_id, is_tree=True)
return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid) return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid)
@staticmethod @staticmethod
@@ -356,6 +386,9 @@ class PreferenceManager(object):
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False): for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
i.soft_delete() i.soft_delete()
for i in PreferenceCITypeOrder.get_by(type_id=type_id, uid=uid, to_dict=False):
i.soft_delete()
@staticmethod @staticmethod
def can_edit_relation(parent_id, child_id): def can_edit_relation(parent_id, child_id):
views = PreferenceRelationView.get_by(to_dict=False) views = PreferenceRelationView.get_by(to_dict=False)
@@ -381,3 +414,36 @@ class PreferenceManager(object):
return False return False
return True return True
@staticmethod
def add_ci_type_order_item(type_id, is_tree=False):
max_order = PreferenceCITypeOrder.get_by(
uid=current_user.uid, is_tree=is_tree, only_query=True).order_by(PreferenceCITypeOrder.order.desc()).first()
order = (max_order and max_order.order + 1) or 1
PreferenceCITypeOrder.create(type_id=type_id, is_tree=is_tree, uid=current_user.uid, order=order)
@staticmethod
def delete_ci_type_order_item(type_id, is_tree=False):
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
first=True, to_dict=False)
existed and existed.soft_delete()
@staticmethod
def upsert_ci_type_order(type_ids, is_tree=False):
for idx, type_id in enumerate(type_ids):
order = idx + 1
existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree,
to_dict=False, first=True)
if existed is not None:
existed.update(order=order, flush=True)
else:
PreferenceCITypeOrder.create(uid=current_user.uid, type_id=type_id, is_tree=is_tree, order=order,
flush=True)
try:
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.error("upsert citype order failed: {}".format(e))
return abort(400, ErrFormat.unknown_error)

View File

@@ -5,10 +5,10 @@ from __future__ import unicode_literals
import copy import copy
import imp import imp
import os
import tempfile
import jinja2 import jinja2
import os
import re
import tempfile
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from jinja2schema import infer from jinja2schema import infer
@@ -117,6 +117,11 @@ class AttributeValueManager(object):
if type_attr and type_attr.is_required and not value and value != 0: if type_attr and type_attr.is_required and not value and value != 0:
return abort(400, ErrFormat.attribute_value_required.format(attr.alias)) return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
@staticmethod
def check_re(expr, value):
if not re.compile(expr).match(str(value)):
return abort(400, ErrFormat.attribute_value_invalid.format(value))
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None): def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
ci = ci or {} ci = ci or {}
v = self._deserialize_value(attr.value_type, value) v = self._deserialize_value(attr.value_type, value)
@@ -130,6 +135,9 @@ class AttributeValueManager(object):
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,): if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
v = None v = None
if attr.re_check and value:
self.check_re(attr.re_check, value)
return v return v
@staticmethod @staticmethod

View File

@@ -2,7 +2,6 @@
import datetime import datetime
from sqlalchemy.dialects.mysql import DOUBLE from sqlalchemy.dialects.mysql import DOUBLE
from api.extensions import db from api.extensions import db
@@ -94,6 +93,8 @@ class Attribute(Model):
_choice_web_hook = db.Column('choice_web_hook', db.JSON) _choice_web_hook = db.Column('choice_web_hook', db.JSON)
choice_other = db.Column(db.JSON) choice_other = db.Column(db.JSON)
re_check = db.Column(db.Text)
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)
option = db.Column(db.JSON) option = db.Column(db.JSON)
@@ -464,6 +465,15 @@ class PreferenceSearchOption(Model):
option = db.Column(db.JSON) option = db.Column(db.JSON)
class PreferenceCITypeOrder(Model):
__tablename__ = "c_pcto"
uid = db.Column(db.Integer, index=True, nullable=False)
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
order = db.Column(db.SmallInteger, default=0)
is_tree = db.Column(db.Boolean, default=False) # True is tree view, False is resource view
# custom # custom
class CustomDashboard(Model): class CustomDashboard(Model):
__tablename__ = "c_c_d" __tablename__ = "c_c_d"

View File

@@ -465,16 +465,16 @@ class CITypeGrantView(APIView):
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False) acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
if request.values.get('ci_filter') or request.values.get('attr_filter'): resource = None
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values) if 'ci_filter' in request.values or 'attr_filter' in request.values:
else: resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
if not resource:
from api.tasks.acl import role_rebuild from api.tasks.acl import role_rebuild
from api.lib.perm.acl.const import ACL_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
app_id = AppCache.get('cmdb').id app_id = AppCache.get('cmdb').id
current_app.logger.info((rid, app_id))
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE) role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
current_app.logger.info('done')
return self.jsonify(code=200) return self.jsonify(code=200)

View File

@@ -2,6 +2,7 @@
from flask import abort from flask import abort
from flask import current_app
from flask import request from flask import request
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
@@ -187,3 +188,15 @@ class PreferenceRelationRevokeView(APIView):
acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms) acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms)
return self.jsonify(code=200) return self.jsonify(code=200)
class PreferenceCITypeOrderView(APIView):
url_prefix = ("/preference/ci_types/order",)
def post(self):
type_ids = request.values.get("type_ids")
is_tree = request.values.get("is_tree") in current_app.config.get('BOOL_TRUE')
PreferenceManager.upsert_ci_type_order(type_ids, is_tree)
return self.jsonify(type_ids=type_ids, is_tree=is_tree)

View File

@@ -8,7 +8,7 @@ elasticsearch==7.17.9
email-validator==1.3.1 email-validator==1.3.1
environs==4.2.0 environs==4.2.0
flasgger==0.9.5 flasgger==0.9.5
Flask==2.3.2 Flask==2.2.5
Flask-Bcrypt==1.0.1 Flask-Bcrypt==1.0.1
flask-babel==4.0.0 flask-babel==4.0.0
Flask-Caching==2.0.2 Flask-Caching==2.0.2
@@ -46,7 +46,7 @@ supervisor==4.0.3
timeout-decorator==0.5.0 timeout-decorator==0.5.0
toposort==1.10 toposort==1.10
treelib==1.6.1 treelib==1.6.1
Werkzeug>=2.3.6 Werkzeug==2.2.3
WTForms==3.0.0 WTForms==3.0.0
shamir~=17.12.0 shamir~=17.12.0
pycryptodomex>=3.19.0 pycryptodomex>=3.19.0

View File

@@ -13,7 +13,7 @@ const getAntdSerials = (color) => {
const themePluginOption = { const themePluginOption = {
fileName: 'css/theme-colors-[contenthash:8].css', fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials('#1890ff'), // 主色系列 matchColors: getAntdSerials('#2f54eb'), // 主色系列
// 改变样式选择器,解决样式覆盖问题 // 改变样式选择器,解决样式覆盖问题
changeSelector (selector) { changeSelector (selector) {
switch (selector) { switch (selector) {

View File

@@ -0,0 +1,2 @@
import Pager from './index.vue'
export default Pager

View File

@@ -0,0 +1,137 @@
<template>
<div>
<a-row class="row" type="flex" justify="end">
<a-col>
<a-space align="end">
<a-button
class="left-button"
size="small"
:disabled="prevIsDisabled"
@click="prevPage"
><a-icon
type="left"
/></a-button>
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
<a-button
class="right-button"
size="small"
:disabled="nextIsDisabled"
@click="nextPage"
><a-icon
type="right"
/></a-button>
<a-dropdown
class="dropdown"
size="small"
placement="topCenter"
:trigger="['click']"
:disabled="dropdownIsDisabled"
>
<a-menu slot="overlay">
<a-menu-item v-for="(size, index) in pageSizes" :key="index" @click="handleItemClick(size)">
{{ `${size}${$t('itemsPerPage')}` }}
</a-menu-item>
</a-menu>
<a-button size="small">{{ `${pageSize}${$t('itemsPerPage')}` }}<a-icon type="down" /> </a-button>
</a-dropdown>
</a-space>
</a-col>
</a-row>
</div>
</template>
<script>
export default {
name: 'Pager',
props: {
currentPage: {
type: Number,
required: true,
},
pageSize: {
type: Number,
required: true,
},
pageSizes: {
type: Array,
required: true,
},
total: {
type: Number,
required: true,
},
isLoading: {
type: Boolean,
required: false,
},
},
data() {
return {
dropdownIsDisabled: false,
prevIsDisabled: true,
}
},
computed: {
nextIsDisabled() {
return this.isLoading || this.total < this.pageSize
},
},
watch: {
isLoading: {
immediate: true,
handler: function(val) {
if (val) {
this.dropdownIsDisabled = true
this.prevIsDisabled = true
} else {
this.dropdownIsDisabled = false
if (this.currentPage === 1) {
this.prevIsDisabled = true
} else {
this.prevIsDisabled = false
}
}
},
},
currentPage: {
immediate: true,
handler: function(val) {
if (val === 1) {
this.prevIsDisabled = true
}
},
},
},
methods: {
handleItemClick(size) {
this.$emit('showSizeChange', size)
},
nextPage() {
const pageNum = this.currentPage + 1
this.$emit('change', pageNum)
},
prevPage() {
const pageNum = this.currentPage - 1
this.$emit('change', pageNum)
},
},
}
</script>
<style lang="less" scoped>
.row {
margin-top: 5px;
.left-button {
padding: 0;
width: 24px;
}
.right-button {
padding: 0;
width: 24px;
}
.page-button {
padding: 0;
width: 24px;
}
}
</style>

View File

@@ -0,0 +1,19 @@
/* eslint-disable no-useless-escape */
import i18n from '@/lang'
export const regList = () => {
return [
{ id: 'letter', label: i18n.t('regexSelect.letter'), value: '^[A-Za-z]+$', message: '请输入字母' },
{ id: 'number', label: i18n.t('regexSelect.number'), value: '^-?(?!0\\d+)\\d+(\\.\\d+)?$', message: '请输入数字' },
{ id: 'letterAndNumber', label: i18n.t('regexSelect.letterAndNumber'), value: '^[A-Za-z0-9.]+$', message: '请输入字母和数字' },
{ id: 'phone', label: i18n.t('regexSelect.phone'), value: '^1[3-9]\\d{9}$', message: '请输入正确手机号码' },
{ id: 'landline', label: i18n.t('regexSelect.landline'), value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$', message: '请输入正确座机' },
{ id: 'zipCode', label: i18n.t('regexSelect.zipCode'), value: '^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$', message: '请输入正确邮政编码' },
{ id: 'IDCard', label: i18n.t('regexSelect.IDCard'), value: '(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)', message: '请输入正确身份证号' },
{ id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' },
{ id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' },
{ id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' },
{ id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\.\\d{1,2})?$', message: '请输入货币金额' },
{ id: 'custom', label: i18n.t('regexSelect.custom'), value: '', message: '' }
]
}

View File

@@ -0,0 +1,2 @@
import RegexSelect from './regexSelect.vue'
export default RegexSelect

View File

@@ -0,0 +1,208 @@
<template>
<a-popover
trigger="click"
placement="bottom"
ref="regexSelect"
overlayClassName="regex-select-wrapper"
:overlayStyle="{ '--overlay-width': `${width}px` }"
@visibleChange="visibleChange"
>
<div class="regex-select" slot="content">
<div class="regex-select-left">
<div class="regex-select-left-header">{{ $t('regexSelect.limitedFormat') }}</div>
<div
@click="
() => {
current = reg
testInput = ''
showMessage = false
}
"
:class="{
'regex-select-left-reg': true,
'regex-select-left-reg-selected': current && current.label === reg.label,
}"
v-for="(reg, index) in regList"
:key="reg.label"
>
<a-divider :style="{ margin: '2px -12px', width: 'calc(100% + 24px)' }" v-if="index === regList.length - 1" />
{{ reg.label }}
</div>
</div>
<div class="regex-select-right">
<template v-if="current">
<div class="regex-select-right-header">{{ $t('regexSelect.regExp') }}</div>
<div
v-if="current.label !== $t('regexSelect.custom')"
:style="{ color: '#000', fontSize: '12px', margin: '12px 0' }"
>
{{ current.value }}
</div>
<a-input
:style="{ margin: '12px 0' }"
size="small"
v-else
v-model="current.value"
@change="
() => {
this.$emit('change', current)
}
"
/>
<template v-if="isShowErrorMsg">
<div class="regex-select-right-header">{{ $t('regexSelect.errMsg') }}</div>
<a-input :style="{ margin: '12px 0' }" size="small" v-model="current.message" />
</template>
<div class="regex-select-right-header">{{ $t('regexSelect.test') }}</div>
<a-input v-model="testInput" :style="{ margin: '12px 0 4px' }" size="small" @change="validate" />
<span :style="{ color: 'red', fontSize: '12px' }" v-if="showMessage">{{
locale === 'zh' ? current.message || '错误' : $t('regexSelect.error')
}}</span>
</template>
</div>
</div>
<a-input ref="regInput" :placeholder="$t('regexSelect.placeholder')" :value="current.label" @change="changeLabel">
</a-input>
</a-popover>
</template>
<script>
import { mapState } from 'vuex'
import { regList } from './constants'
export default {
name: 'RegexSelect',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Object,
default: () => {},
},
isShowErrorMsg: {
type: Boolean,
default: true,
},
limitedFormat: {
type: Array,
default: () => [],
},
},
data() {
return {
showMessage: false,
width: 370,
testInput: '',
}
},
computed: {
...mapState(['locale']),
regList() {
if (this.limitedFormat.length) {
return regList().filter((item) => this.limitedFormat.includes(item.id))
}
return regList()
},
current: {
get() {
if (this.value?.value && !this.value?.label) {
const _find = this.regList.find((reg) => reg.value === this.value?.value)
return { ...this.value, label: _find?.label ?? this.$t('regexSelect.custom') }
}
return this.value ?? {}
},
set(val) {
this.showMessage = false
this.$emit('change', val)
return val
},
},
},
mounted() {
this.$nextTick(() => {
const regInput = this.$refs.regInput.$refs.input
this.width = regInput.offsetWidth || 370
})
},
methods: {
validate(e) {
const reg = RegExp(this.current.value, 'g')
this.showMessage = !reg.test(e.target.value)
},
changeLabel(e) {
this.current = {}
},
visibleChange(visible) {
if (visible) {
this.$nextTick(() => {
this.testInput = ''
this.showMessage = false
})
}
},
},
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.regex-select {
width: 100%;
height: 300px;
display: flex;
.regex-select-left {
width: 40%;
height: 100%;
border: 1px solid #cacdd9;
border-radius: 4px;
padding: 12px;
&-reg {
padding-left: 2px;
cursor: pointer;
&-selected,
&:hover {
color: #custom_colors[color_1];
}
}
}
&-right {
flex: 1;
height: 100%;
border: 1px solid #cacdd9;
border-radius: 4px;
margin-left: 8px;
padding: 12px;
}
&-left,
&-right {
&-header {
font-weight: 400;
font-size: 14px;
color: #000000;
border-left: 2px solid #custom_colors[color_1];
padding-left: 6px;
margin-left: -6px;
}
}
}
</style>
<style lang="less">
.regex-select-wrapper {
.ant-popover-arrow {
display: none;
}
.ant-popover-inner-content {
padding: 0;
min-width: 370px;
width: var(--overlay-width);
}
}
.regex-select-wrapper.ant-popover-placement-bottom .ant-popover-content {
margin-top: -8px;
}
.regex-select-wrapper.ant-popover-placement-top .ant-popover-content {
margin-bottom: -8px;
}
</style>

View File

@@ -119,7 +119,8 @@ export default {
border-radius: 4px; border-radius: 4px;
color: @layout-header-font-color; color: @layout-header-font-color;
height: @layout-header-height; height: @layout-header-height;
line-height: @layout-header-height; display: inline-flex;
align-items: center;
&:hover { &:hover {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%); background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
color: @layout-header-font-selected-color; color: @layout-header-font-selected-color;

View File

@@ -71,8 +71,6 @@ export default {
title: this.$t('tip'), title: this.$t('tip'),
content: this.$t('topMenu.confirmLogout'), content: this.$t('topMenu.confirmLogout'),
onOk() { onOk() {
// localStorage.removeItem('ops_cityps_currentId')
localStorage.clear()
return that.Logout() return that.Logout()
}, },
onCancel() {}, onCancel() {},

View File

@@ -14,7 +14,7 @@
*/ */
export default { export default {
primaryColor: '#1890ff', // primary color of ant design primaryColor: '#2f54eb', // primary color of ant design
navTheme: 'dark', // theme for nav menu navTheme: 'dark', // theme for nav menu
layout: 'sidemenu', // nav menu position: sidemenu or topmenu layout: 'sidemenu', // nav menu position: sidemenu or topmenu
contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu

View File

@@ -145,6 +145,26 @@ export default {
sizeLimit: 'The image size cannot exceed 2MB!', sizeLimit: 'The image size cannot exceed 2MB!',
nodata: 'There are currently no custom icons available. Click here to upload' nodata: 'There are currently no custom icons available. Click here to upload'
}, },
regexSelect: {
limitedFormat: 'Limited Format',
regExp: 'RegExp',
errMsg: 'Error Message',
test: 'Test',
placeholder: 'Please Select RegExp',
error: 'Error',
letter: 'letter',
number: 'number',
letterAndNumber: 'letter&number',
phone: 'phone',
landline: 'landline',
zipCode: 'zip code',
IDCard: 'ID card',
ip: 'IP',
email: 'email',
link: 'link',
monetaryAmount: 'monetary amount',
custom: 'custom',
},
cmdb: cmdb_en, cmdb: cmdb_en,
cs: cs_en, cs: cs_en,
acl: acl_en, acl: acl_en,

View File

@@ -145,6 +145,26 @@ export default {
sizeLimit: '图片大小不可超过2MB', sizeLimit: '图片大小不可超过2MB',
nodata: '暂无自定义图标,点击此处上传' nodata: '暂无自定义图标,点击此处上传'
}, },
regexSelect: {
limitedFormat: '限定格式',
regExp: '正则表达式',
errMsg: '错误时提示',
test: '测试',
placeholder: '请选择正则表达式',
error: '错误',
letter: '字母',
number: '数字',
letterAndNumber: '字母和数字',
phone: '手机号码',
landline: '座机',
zipCode: '邮政编码',
IDCard: '身份证号',
ip: 'IP地址',
email: '邮箱',
link: '链接',
monetaryAmount: '货币金额',
custom: '自定义',
},
cmdb: cmdb_zh, cmdb: cmdb_zh,
cs: cs_zh, cs: cs_zh,
acl: acl_zh, acl: acl_zh,

View File

@@ -1,116 +0,0 @@
<template>
<div>
<a-row class="row" type="flex" justify="end">
<a-col>
<a-space align="end">
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button>
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
<a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button>
<a-dropdown class="dropdown" size="small" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
<a-menu slot="overlay">
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
{{ size }}{{ $t('itemsPerPage') }}
</a-menu-item>
</a-menu>
<a-button size="small"> {{ pageSize }}{{ $t('itemsPerPage') }}<a-icon type="down" /> </a-button>
</a-dropdown>
</a-space>
</a-col>
</a-row>
</div>
</template>
<script>
export default {
props: {
currentPage: {
type: Number,
required: true
},
pageSize: {
type: Number,
required: true
},
pageSizes: {
type: Array,
required: true
},
total: {
type: Number,
required: true
},
isLoading: {
type: Boolean,
required: false
}
},
data() {
return {
dropdownIsDisabled: false,
prevIsDisabled: true,
}
},
computed: {
nextIsDisabled() {
return this.isLoading || this.total < this.pageSize
}
},
watch: {
isLoading: {
immediate: true,
handler: function (val) {
if (val === true) {
this.dropdownIsDisabled = true
this.prevIsDisabled = true
} else {
this.dropdownIsDisabled = false
if (this.currentPage === 1) {
this.prevIsDisabled = true
} else {
this.prevIsDisabled = false
}
}
}
},
currentPage: {
immediate: true,
handler: function (val) {
if (val === 1) {
this.prevIsDisabled = true
}
}
}
},
methods: {
handleItemClick(size) {
this.$emit('showSizeChange', size)
},
nextPage() {
const pageNum = this.currentPage + 1
this.$emit('change', pageNum)
},
prevPage() {
const pageNum = this.currentPage - 1
this.$emit('change', pageNum)
}
}
}
</script>
<style lang="less" scoped>
.row{
margin-top: 5px;
.left-button{
padding: 0;
width: 24px;
}
.right-button{
padding: 0;
width: 24px;
}
.page-button{
padding: 0;
width: 24px;
}
}
</style>

View File

@@ -79,7 +79,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from './pager.vue' import Pager from '@/components/Pager'
import SearchForm from './searchForm.vue' import SearchForm from './searchForm.vue'
import { searchPermissonHistory } from '@/modules/acl/api/history' import { searchPermissonHistory } from '@/modules/acl/api/history'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'

View File

@@ -62,7 +62,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from './pager.vue' import Pager from '@/components/Pager'
import SearchForm from './searchForm.vue' import SearchForm from './searchForm.vue'
import { searchResourceHistory } from '@/modules/acl/api/history' import { searchResourceHistory } from '@/modules/acl/api/history'
export default { export default {

View File

@@ -57,7 +57,7 @@
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operate" :title="$t('batchOperate')"> <vxe-column field="operate" :title="$t('acl.batchOperate')">
<template #default="{row}"> <template #default="{row}">
<a-button size="small" type="danger" @click="handleClearAll(row)"> <a-button size="small" type="danger" @click="handleClearAll(row)">
{{ $t('clear') }} {{ $t('clear') }}

View File

@@ -56,7 +56,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from './pager.vue' import Pager from '@/components/Pager'
import SearchForm from './searchForm.vue' import SearchForm from './searchForm.vue'
import { searchResourceHistory } from '@/modules/acl/api/history' import { searchResourceHistory } from '@/modules/acl/api/history'
export default { export default {

View File

@@ -1,4 +1,4 @@
<template> <template>
<CustomDrawer <CustomDrawer
width="800px" width="800px"
placement="left" placement="left"

View File

@@ -76,7 +76,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from './pager.vue' import Pager from '@/components/Pager'
import SearchForm from './searchForm.vue' import SearchForm from './searchForm.vue'
import { searchRoleHistory } from '@/modules/acl/api/history' import { searchRoleHistory } from '@/modules/acl/api/history'
export default { export default {

View File

@@ -1,4 +1,4 @@
<template> <template>
<CustomDrawer <CustomDrawer
@close="handleClose" @close="handleClose"
width="500" width="500"

View File

@@ -55,7 +55,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from './pager.vue' import Pager from '@/components/Pager'
import SearchForm from './searchForm.vue' import SearchForm from './searchForm.vue'
import { searchTriggerHistory } from '@/modules/acl/api/history' import { searchTriggerHistory } from '@/modules/acl/api/history'
export default { export default {

View File

@@ -81,7 +81,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import Pager from '../../module/pager.vue' import Pager from '@/components/Pager'
import SearchForm from '../../module/searchForm.vue' import SearchForm from '../../module/searchForm.vue'
import { searchApp } from '@/modules/acl/api/app' import { searchApp } from '@/modules/acl/api/app'
import { searchPermissonHistory } from '@/modules/acl/api/history' import { searchPermissonHistory } from '@/modules/acl/api/history'

View File

@@ -64,7 +64,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from '../../module/pager.vue' import Pager from '@/components/Pager'
import SearchForm from '../../module/searchForm.vue' import SearchForm from '../../module/searchForm.vue'
import { searchResourceHistory } from '@/modules/acl/api/history' import { searchResourceHistory } from '@/modules/acl/api/history'
import { searchUser } from '@/modules/acl/api/user' import { searchUser } from '@/modules/acl/api/user'

View File

@@ -58,7 +58,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from '../../module/pager.vue' import Pager from '@/components/Pager'
import SearchForm from '../../module/searchForm.vue' import SearchForm from '../../module/searchForm.vue'
import { searchResourceHistory } from '@/modules/acl/api/history' import { searchResourceHistory } from '@/modules/acl/api/history'
import { searchUser } from '@/modules/acl/api/user' import { searchUser } from '@/modules/acl/api/user'

View File

@@ -76,7 +76,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from '../../module/pager.vue' import Pager from '@/components/Pager'
import SearchForm from '../../module/searchForm.vue' import SearchForm from '../../module/searchForm.vue'
import { searchRoleHistory } from '@/modules/acl/api/history' import { searchRoleHistory } from '@/modules/acl/api/history'
import { searchApp } from '@/modules/acl/api/app' import { searchApp } from '@/modules/acl/api/app'

View File

@@ -57,7 +57,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import Pager from '../../module/pager.vue' import Pager from '@/components/Pager'
import SearchForm from '../../module/searchForm.vue' import SearchForm from '../../module/searchForm.vue'
import { searchTriggerHistory } from '@/modules/acl/api/history' import { searchTriggerHistory } from '@/modules/acl/api/history'
import { getTriggers } from '@/modules/acl/api/trigger' import { getTriggers } from '@/modules/acl/api/trigger'

View File

@@ -57,17 +57,20 @@
</template> </template>
</vxe-table-column> </vxe-table-column>
</ops-table> </ops-table>
<vxe-pager <a-pagination
size="small" size="small"
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']" show-size-changer
:current-page.sync="tablePage.currentPage" show-quick-jumper
:page-size.sync="tablePage.pageSize" :current="tablePage.currentPage"
:total="tablePage.total" :total="tablePage.total"
:page-sizes="pageSizeOptions" :show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
@page-change="handlePageChange" :page-size="tablePage.pageSize"
:style="{ marginTop: '10px' }" :default-current="1"
> :page-size-options="pageSizeOptions"
</vxe-pager> @change="pageOrSizeChange"
@showSizeChange="pageOrSizeChange"
:style="{ marginTop: '10px', textAlign: 'right' }"
/>
</a-spin> </a-spin>
<resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm> <resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm>
@@ -89,7 +92,7 @@ export default {
loading: false, loading: false,
groups: [], groups: [],
id2perms: {}, id2perms: {},
pageSizeOptions: [10, 25, 50, 100], pageSizeOptions: ['20', '50', '100', '200'],
tablePage: { tablePage: {
total: 0, total: 0,
currentPage: 1, currentPage: 1,
@@ -176,7 +179,7 @@ export default {
this.handleOk() this.handleOk()
}) })
}, },
handlePageChange({ currentPage, pageSize }) { pageOrSizeChange(currentPage, pageSize) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
this.tablePage.pageSize = pageSize this.tablePage.pageSize = pageSize
this.searchData() this.searchData()

View File

@@ -77,7 +77,7 @@
<!-- 2 --> <!-- 2 -->
<vxe-table-column field="name" :title="$t('acl.resourceName')" :min-widh="150" fixed="left" show-overflow> <vxe-table-column field="name" :title="$t('acl.resourceName')" :min-widh="150" fixed="left" show-overflow>
<template #title="{row}"> <template #title="{ row }">
{{ row.isGroup ? $t('acl.groupName') : $t('acl.resourceName') }} {{ row.isGroup ? $t('acl.groupName') : $t('acl.resourceName') }}
</template> </template>
</vxe-table-column> </vxe-table-column>
@@ -86,10 +86,12 @@
<vxe-table-column field="user" :title="$t('acl.creator')" :min-widh="100"> </vxe-table-column> <vxe-table-column field="user" :title="$t('acl.creator')" :min-widh="100"> </vxe-table-column>
<!-- 4 --> <!-- 4 -->
<vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center"> </vxe-table-column> <vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center">
</vxe-table-column>
<!-- 5 --> <!-- 5 -->
<vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center"> </vxe-table-column> <vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center">
</vxe-table-column>
<!-- 6 --> <!-- 6 -->
@@ -99,8 +101,9 @@
:min-widh="200" :min-widh="200"
fixed="right" fixed="right"
align="center" align="center"
show-overflow> show-overflow
<template #default="{row}"> >
<template #default="{ row }">
<span v-show="isGroup"> <span v-show="isGroup">
<a @click="handleDisplayMember(row)">{{ $t('acl.member') }}</a> <a @click="handleDisplayMember(row)">{{ $t('acl.member') }}</a>
<a-divider type="vertical" /> <a-divider type="vertical" />
@@ -117,27 +120,36 @@
</a> </a>
</a-tooltip> </a-tooltip>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-popconfirm :title="$t('confirmDelete')" @confirm="handleDelete(row)" @cancel="cancel" :okText="$t('yes')" :cancelText="$t('no')"> <a-popconfirm
:title="$t('confirmDelete')"
@confirm="handleDelete(row)"
@cancel="cancel"
:okText="$t('yes')"
:cancelText="$t('no')"
>
<a style="color: red"><a-icon type="delete"/></a> <a style="color: red"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</template> </template>
</vxe-table-column> </vxe-table-column>
</ops-table> </ops-table>
<vxe-pager <a-pagination
size="small" size="small"
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']" show-size-changer
:current-page.sync="tablePage.currentPage" show-quick-jumper
:page-size.sync="tablePage.pageSize" :current="tablePage.currentPage"
:total="tablePage.total" :total="tablePage.total"
:page-sizes="pageSizeOptions" :show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
@page-change="handlePageChange" :page-size="tablePage.pageSize"
:style="{ marginTop: '10px' }" :default-current="1"
> :page-size-options="pageSizeOptions"
</vxe-pager> @change="pageOrSizeChange"
@showSizeChange="pageOrSizeChange"
:style="{ marginTop: '10px', textAlign: 'right' }"
/>
</a-spin> </a-spin>
</div> </div>
<div v-else style="text-align: center;margin-top:20%"> <div v-else style="text-align: center; margin-top: 20%">
<a-icon style="font-size:50px; margin-bottom: 20px; color: orange" type="info-circle" /> <a-icon style="font-size: 50px; margin-bottom: 20px; color: orange" type="info-circle" />
<h3>{{ $t('acl.addTypeTips') }}</h3> <h3>{{ $t('acl.addTypeTips') }}</h3>
</div> </div>
<resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm> <resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm>
@@ -191,7 +203,7 @@ export default {
isGroup: false, isGroup: false,
allResourceTypes: [], allResourceTypes: [],
currentType: { id: 0 }, currentType: { id: 0 },
pageSizeOptions: [10, 25, 50, 100], pageSizeOptions: ['20', '50', '100', '200'],
searchName: '', searchName: '',
selectedRows: [], selectedRows: [],
} }
@@ -291,7 +303,7 @@ export default {
} }
}, },
cancel() {}, cancel() {},
handlePageChange({ currentPage, pageSize }) { pageOrSizeChange(currentPage, pageSize) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
this.tablePage.pageSize = pageSize this.tablePage.pageSize = pageSize
this.searchData() this.searchData()
@@ -302,7 +314,6 @@ export default {
.getVxetableRef() .getVxetableRef()
.getCheckboxRecords() .getCheckboxRecords()
.concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords()) .concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords())
console.log(this.selectedRows)
}, },
onSelectRangeEnd({ records }) { onSelectRangeEnd({ records }) {
this.selectedRows = records this.selectedRows = records

View File

@@ -42,13 +42,13 @@
<!-- 2 --> <!-- 2 -->
<vxe-table-column field="is_app_admin" :title="$t('admin')" :min-width="100" align="center"> <vxe-table-column field="is_app_admin" :title="$t('admin')" :min-width="100" align="center">
<template #default="{row}"> <template #default="{ row }">
<a-icon type="check" v-if="row.is_app_admin" /> <a-icon type="check" v-if="row.is_app_admin" />
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="id" :title="$t('acl.inheritedFrom')" :min-width="150"> <vxe-table-column field="id" :title="$t('acl.inheritedFrom')" :min-width="150">
<template #default="{row}"> <template #default="{ row }">
<a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag> <a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag>
</template> </template>
</vxe-table-column> </vxe-table-column>
@@ -69,13 +69,13 @@
} }
" "
> >
<template #default="{row}"> <template #default="{ row }">
{{ row.uid ? $t('no') : $t('yes') }} {{ row.uid ? $t('no') : $t('yes') }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="action" :title="$t('operation')" :width="120" fixed="right"> <vxe-table-column field="action" :title="$t('operation')" :width="120" fixed="right">
<template #default="{row}"> <template #default="{ row }">
<a-space> <a-space>
<a-tooltip :title="$t('acl.resourceList')"> <a-tooltip :title="$t('acl.resourceList')">
<a <a
@@ -104,17 +104,20 @@
</template> </template>
</vxe-table-column> </vxe-table-column>
</ops-table> </ops-table>
<vxe-pager <a-pagination
size="small" size="small"
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']" show-size-changer
:current-page.sync="tablePage.currentPage" show-quick-jumper
:page-size.sync="tablePage.pageSize" :current="tablePage.currentPage"
:total="tablePage.total" :total="tablePage.total"
:page-sizes="pageSizeOptions" :show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
@page-change="handlePageChange" :page-size="tablePage.pageSize"
:style="{ marginTop: '10px' }" :default-current="1"
> :page-size-options="pageSizeOptions"
</vxe-pager> @change="pageOrSizeChange"
@showSizeChange="pageOrSizeChange"
:style="{ marginTop: '10px', textAlign: 'right' }"
/>
</a-spin> </a-spin>
<roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm> <roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm>
@@ -149,7 +152,7 @@ export default {
tableData: [], tableData: [],
allRoles: [], allRoles: [],
id2parents: {}, id2parents: {},
pageSizeOptions: [10, 25, 50, 100], pageSizeOptions: ['20', '50', '100', '200'],
searchName: '', searchName: '',
filterTableValue: { user_role: 1, user_only: 0 }, filterTableValue: { user_role: 1, user_only: 0 },
} }
@@ -254,7 +257,7 @@ export default {
cancel(e) { cancel(e) {
return false return false
}, },
handlePageChange({ currentPage, pageSize }) { pageOrSizeChange(currentPage, pageSize) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
this.tablePage.pageSize = pageSize this.tablePage.pageSize = pageSize
this.loadData() this.loadData()

View File

@@ -114,3 +114,12 @@ export function revokeRelationView(rid, data) {
data: data data: data
}) })
} }
// preference citype order
export function preferenceCitypeOrder(data) {
return axios({
url: `/v0.1/preference/ci_types/order`,
method: 'POST',
data: data
})
}

View File

@@ -174,7 +174,8 @@ const cmdb_en = {
date: 'Date', date: 'Date',
time: 'Time', time: 'Time',
json: 'JSON', json: 'JSON',
event: 'Event' event: 'Event',
reg: 'Regex'
}, },
components: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@@ -366,12 +367,12 @@ const cmdb_en = {
ad: { ad: {
upload: 'Import', upload: 'Import',
download: 'Export', download: 'Export',
accpet: 'Accept', accept: 'Accept',
accpetBy: 'Accept By', acceptBy: 'Accept By',
acceptTime: 'Accept Time', acceptTime: 'Accept Time',
confirmAccept: 'Confirm Accept?', confirmAccept: 'Confirm Accept?',
accpetSuccess: 'Accept successfully', acceptSuccess: 'Accept successfully',
isAccpet: 'Is accept', isAccept: 'Is accept',
deleteADC: 'Confirm to delete this data?', deleteADC: 'Confirm to delete this data?',
batchDelete: 'Confirm to delete this data?', batchDelete: 'Confirm to delete this data?',
agent: 'Built-in & Plug-ins', agent: 'Built-in & Plug-ins',

View File

@@ -174,7 +174,8 @@ const cmdb_zh = {
date: '日期', date: '日期',
time: '时间', time: '时间',
json: 'JSON', json: 'JSON',
event: '事件' event: '事件',
reg: '正则校验'
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@@ -366,12 +367,12 @@ const cmdb_zh = {
ad: { ad: {
upload: '规则导入', upload: '规则导入',
download: '规则导出', download: '规则导出',
accpet: '入库', accept: '入库',
accpetBy: '入库人', acceptBy: '入库人',
acceptTime: '入库时间', acceptTime: '入库时间',
confirmAccept: '确认入库?', confirmAccept: '确认入库?',
accpetSuccess: '入库成功', acceptSuccess: '入库成功',
isAccpet: '是否入库', isAccept: '是否入库',
deleteADC: '确认删除该条数据?', deleteADC: '确认删除该条数据?',
batchDelete: '确认删除这些数据?', batchDelete: '确认删除这些数据?',
agent: '内置 & 插件', agent: '内置 & 插件',

View File

@@ -138,7 +138,7 @@ const genCmdbRoutes = async () => {
const [preference, relation] = await Promise.all([getPreference(), getRelationView()]) const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
preference.forEach(item => { preference.forEach(item => {
routes.children[2].children.unshift({ routes.children[2].children.push({
path: `/cmdb/instances/types/${item.id}`, path: `/cmdb/instances/types/${item.id}`,
component: () => import(`../views/ci/index`), component: () => import(`../views/ci/index`),
name: `cmdb_${item.id}`, name: `cmdb_${item.id}`,

View File

@@ -101,7 +101,6 @@
:cell-type="col.value_type === '2' ? 'string' : 'auto'" :cell-type="col.value_type === '2' ? 'string' : 'auto'"
:fixed="col.is_fixed ? 'left' : ''" :fixed="col.is_fixed ? 'left' : ''"
> >
<!-- <template #edit="{row}"><a-input v-model="row[col.field]"></a-input></template> -->
<template #header> <template #header>
<span class="vxe-handle"> <span class="vxe-handle">
<OpsMoveIcon <OpsMoveIcon
@@ -110,7 +109,7 @@
<span>{{ col.title }}</span> <span>{{ col.title }}</span>
</span> </span>
</template> </template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }"> <template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
@@ -147,18 +146,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select
:getPopupContainer="(trigger) => trigger.parentElement"
:style="{ width: '100%', height: '32px' }"
v-model="row[col.field]"
:placeholder="$t('placeholder2')"
v-else-if="col.is_list"
:showArrow="false"
mode="tags"
class="ci-table-edit-select"
allowClear
>
</a-select>
</template> </template>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -293,8 +280,6 @@
</a-pagination> </a-pagination>
</div> </div>
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" /> <create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
<!-- <EditAttrsDrawer ref="editAttrsDrawer" @refresh="refreshAfterEditAttrs" /> -->
<!-- <batch-update-relation :typeId="typeId" ref="batchUpdateRelation" @submit="batchUpdateRelation" /> -->
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" /> <BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
<MetadataDrawer ref="metadataDrawer" /> <MetadataDrawer ref="metadataDrawer" />
@@ -471,11 +456,6 @@ export default {
const regSort = /(?<=sort=).+/g const regSort = /(?<=sort=).+/g
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
// if (exp) {
// exp = exp.replace(/(\:)/g, '$1*')
// exp = exp.replace(/(\,)/g, '*$1')
// }
// If the sorting is done by clicking on the table, the table will prevail.
let sort let sort
if (sortByTable) { if (sortByTable) {
sort = sortByTable sort = sortByTable
@@ -484,7 +464,6 @@ export default {
} }
const res = await searchCI({ const res = await searchCI({
q: `_type:${this.typeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`, q: `_type:${this.typeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
// q: `${this.mergeQ(queryParams)}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
count: this.pageSize, count: this.pageSize,
page: this.currentPage, page: this.currentPage,
sort, sort,
@@ -533,55 +512,17 @@ export default {
this.$refs['xTable'].getVxetableRef().setCheckboxRow(rows, true) this.$refs['xTable'].getVxetableRef().setCheckboxRow(rows, true)
} }
}, },
// mergeQ(params) {
// let q = `_type:${this.typeId}`
// Object.keys(params).forEach((key) => {
// if (!['pageNo', 'pageSize', 'sortField', 'sortOrder'].includes(key) && params[key] + '' !== '') {
// if (typeof params[key] === 'object' && params[key] && params[key].length > 1) {
// q += `,${key}:(${params[key].join(';')})`
// } else if (params[key]) {
// q += `,${key}:*${params[key]}*`
// }
// }
// })
// return q
// },
async loadPreferenceAttrList() { async loadPreferenceAttrList() {
const subscribed = await getSubscribeAttributes(this.typeId) const subscribed = await getSubscribeAttributes(this.typeId)
this.preferenceAttrList = subscribed.attributes // All columns that have been subscribed this.preferenceAttrList = subscribed.attributes // All columns that have been subscribed
}, },
onSelectChange() { onSelectChange() {
// const current = records.map((i) => i.ci_id || i._id)
// const cached = new Set(this.selectedRowKeys)
// if (checked) {
// current.forEach((i) => {
// cached.add(i)
// })
// } else {
// if (row) {
// cached.delete(row.ci_id || row._id)
// } else {
// this.instanceList.map((row) => {
// cached.delete(row.ci_id || row._id)
// })
// }
// }
const xTable = this.$refs.xTable.getVxetableRef() const xTable = this.$refs.xTable.getVxetableRef()
const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()] const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
this.selectedRowKeys = records.map((i) => i.ci_id || i._id) this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
}, },
onSelectRangeEnd({ records }) { onSelectRangeEnd({ records }) {
// const current = records.map((i) => i.ci_id || i._id)
// const cached = new Set(this.selectedRowKeys)
// current.forEach((i) => {
// cached.add(i)
// })
this.selectedRowKeys = records.map((i) => i.ci_id || i._id) this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
// this.setSelectRows()
}, },
reloadData() { reloadData() {
this.loadTableData() this.loadTableData()
@@ -623,7 +564,7 @@ export default {
}) })
.catch((err) => { .catch((err) => {
console.log(err) console.log(err)
$table.revertData(row) this.loadTableData()
}) })
} }
this.columns.forEach((col) => { this.columns.forEach((col) => {
@@ -694,12 +635,22 @@ export default {
} }
}) })
this.$refs.create.visible = false this.$refs.create.visible = false
const key = 'updatable'
let errorMsg = ''
for (let i = 0; i < this.selectedRowKeys.length; i++) { for (let i = 0; i < this.selectedRowKeys.length; i++) {
await updateCI(this.selectedRowKeys[i], payload, false) await updateCI(this.selectedRowKeys[i], payload, false)
.then(() => { .then(() => {
successNum += 1 successNum += 1
}) })
.catch(() => { .catch((error) => {
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
this.$notification.warning({
key,
message: this.$t('warning'),
description: errorMsg,
duration: 0,
style: { whiteSpace: 'break-spaces' },
})
errorNum += 1 errorNum += 1
}) })
.finally(() => { .finally(() => {

View File

@@ -24,7 +24,9 @@
/> />
</template> </template>
<template v-if="parentsType && parentsType.length"> <template v-if="parentsType && parentsType.length">
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ $t('cmdb.menu.citypeRelation') }}</a-divider> <a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{
$t('cmdb.menu.citypeRelation')
}}</a-divider>
<a-form> <a-form>
<a-row :gutter="24" align="top" type="flex"> <a-row :gutter="24" align="top" type="flex">
<a-col :span="12" v-for="item in parentsType" :key="item.id"> <a-col :span="12" v-for="item in parentsType" :key="item.id">
@@ -40,7 +42,11 @@
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input :placeholder="$t('cmdb.ci.tips1')" v-model="parentsForm[item.name].value" style="width: 50%" /> <a-input
:placeholder="$t('cmdb.ci.tips1')"
v-model="parentsForm[item.name].value"
style="width: 50%"
/>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-col> </a-col>

View File

@@ -60,7 +60,7 @@
</span> </span>
</template> </template>
<template v-else-if="attr.is_list"> <template v-else-if="attr.is_list">
<span> {{ ci[attr.name].join(',') }}</span> <span> {{ ci[attr.name] && Array.isArray(ci[attr.name]) ? ci[attr.name].join(',') : ci[attr.name] }}</span>
</template> </template>
<template v-else>{{ getName(ci[attr.name]) }}</template> <template v-else>{{ getName(ci[attr.name]) }}</template>
</span> </span>
@@ -105,23 +105,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select
:style="{ width: '100%' }"
v-decorator="[
attr.name,
{
rules: [{ required: attr.is_required }],
},
]"
:placeholder="$t('placeholder2')"
v-else-if="attr.is_list"
mode="tags"
showSearch
allowClear
size="small"
:getPopupContainer="(trigger) => trigger.parentElement"
>
</a-select>
<a-input-number <a-input-number
size="small" size="small"
v-decorator="[ v-decorator="[
@@ -131,7 +114,7 @@
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else-if="attr.value_type === '0' || attr.value_type === '1'" v-else-if="(attr.value_type === '0' || attr.value_type === '1') && !attr.is_list"
/> />
<a-date-picker <a-date-picker
size="small" size="small"
@@ -144,22 +127,9 @@
style="width: 100%" style="width: 100%"
:format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-else-if="attr.value_type === '4' || attr.value_type === '3'" v-else-if="(attr.value_type === '4' || attr.value_type === '3') && !attr.is_list"
:showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }" :showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }"
/> />
<!-- <a-input
size="small"
@focus="(e) => handleFocusInput(e, attr)"
v-decorator="[
attr.name,
{
validateTrigger: ['submit'],
rules: [{ required: attr.is_required }],
},
]"
style="width: 100%"
v-else-if="attr.value_type === '6'"
/> -->
<a-input <a-input
size="small" size="small"
v-decorator="[ v-decorator="[
@@ -241,7 +211,9 @@ export default {
this.$nextTick(async () => { this.$nextTick(async () => {
if (this.attr.is_list && !this.attr.is_choice) { if (this.attr.is_list && !this.attr.is_choice) {
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: this.ci[this.attr.name] || null, [`${this.attr.name}`]: Array.isArray(this.ci[this.attr.name])
? this.ci[this.attr.name].join(',')
: this.ci[this.attr.name],
}) })
return return
} }

View File

@@ -22,7 +22,12 @@
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null, initialValue:
attr.default && attr.default.default
? attr.is_list
? attr.default.default.split(',')
: attr.default.default
: null,
}, },
]" ]"
:placeholder="$t('placeholder2')" :placeholder="$t('placeholder2')"
@@ -53,19 +58,18 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select <a-input
v-else-if="attr.is_list" v-else-if="attr.is_list"
mode="tags"
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null, initialValue: attr.default && attr.default.default ? attr.default.default : '',
}, },
]" ]"
> >
</a-select> </a-input>
<a-input-number <a-input-number
v-decorator="[ v-decorator="[
attr.name, attr.name,

View File

@@ -1,103 +0,0 @@
<template>
<CustomDrawer
:visible="visible"
width="600"
@close="
() => {
visible = false
}
"
:title="$t('cmdb.ci.attributeSettings')"
>
<CustomTransfer
ref="customTransfer"
:dataSource="attrList"
:showSearch="true"
:listStyle="{
width: '230px',
height: '500px',
}"
:titles="[$t('cmdb.components.unselectAttributes'), $t('cmdb.components.selectAttributes')]"
:render="item => item.title"
:targetKeys="selectedAttrList"
@change="handleChange"
@selectChange="selectChange"
>
<span slot="notFoundContent">{{ $t('noData') }}</span>
</CustomTransfer>
<div class="custom-drawer-bottom-action">
<a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div>
</CustomDrawer>
</template>
<script>
import { subscribeCIType, getSubscribeAttributes } from '@/modules/cmdb/api/preference'
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
export default {
name: 'EditAttrsDrawer',
data() {
return {
attrList: [],
typeId: null,
visible: false,
selectedAttrList: [],
}
},
methods: {
open(typeId) {
this.typeId = typeId
this.getAttrs()
},
getAttrs() {
getCITypeAttributesByName(this.typeId).then(res => {
const attributes = res.attributes
getSubscribeAttributes(this.typeId).then(_res => {
const attrList = []
const selectedAttrList = []
const subAttributes = _res.attributes
this.instanceSubscribed = _res.is_subscribed
subAttributes.forEach(item => {
selectedAttrList.push(item.id.toString())
})
attributes.forEach(item => {
const data = {
key: item.id.toString(),
title: item.alias || item.name,
}
attrList.push(data)
})
this.attrList = attrList
this.selectedAttrList = selectedAttrList
this.visible = true
})
})
},
handleChange(targetKeys, direction, moveKeys) {
this.selectedAttrList = targetKeys
},
handleSubmit() {
const that = this
if (this.selectedAttrList.length) {
subscribeCIType(this.typeId, this.selectedAttrList).then(res => {
this.$message.success(this.$t('cmdb.components.subSuccess'))
this.visible = false
this.$emit('refresh')
})
} else {
this.$confirm({
title: that.$t('warning'),
content: that.$t('cmdb.ci.tips4'),
})
}
},
selectChange(sourceSelectedKeys, targetSelectedKeys) {
this.$refs.customTransfer.dbClick(sourceSelectedKeys, targetSelectedKeys, 'title', 'key')
},
},
}
</script>
<style></style>

View File

@@ -1 +0,0 @@
editAttrsDrawer 这个文件似乎也没用了

View File

@@ -13,7 +13,11 @@
<a-form :form="form" :layout="formLayout"> <a-form :form="form" :layout="formLayout">
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.basicConfig') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.basicConfig') }}</a-divider>
<a-col :span="12"> <a-col :span="12">
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.AttributeName')"> <a-form-item
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
:label="$t('cmdb.ciType.AttributeName')"
>
<a-input <a-input
:disabled="true" :disabled="true"
name="name" name="name"
@@ -35,12 +39,20 @@
</a-col> </a-col>
<a-col <a-col
:span="12" :span="12"
><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('alias')"> ><a-form-item
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
:label="$t('alias')"
>
<a-input name="alias" v-decorator="['alias', { rules: [] }]" /> </a-form-item <a-input name="alias" v-decorator="['alias', { rules: [] }]" /> </a-form-item
></a-col> ></a-col>
<a-col <a-col
:span="12" :span="12"
><a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.ciType.DataType')"> ><a-form-item
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
:label="$t('cmdb.ciType.DataType')"
>
<a-select <a-select
:disabled="true" :disabled="true"
name="value_type" name="value_type"
@@ -59,13 +71,12 @@
:label="$t('cmdb.ciType.defaultValue')" :label="$t('cmdb.ciType.defaultValue')"
> >
<template> <template>
<a-select <a-input
v-if="form.getFieldValue('is_list')" v-if="form.getFieldValue('is_list')"
mode="tags"
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-select> </a-input>
<a-select <a-select
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
mode="tags" mode="tags"
@@ -160,7 +171,11 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')"> <a-form-item
:label-col="{ span: 8 }"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')"
>
<a-switch <a-switch
:disabled="isShowComputedArea" :disabled="isShowComputedArea"
@change="onChange" @change="onChange"
@@ -282,6 +297,11 @@
</a-col> </a-col>
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
<a-row> <a-row>
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" />
</a-form-item>
</a-col>
<a-col :span="24"> <a-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
<FontArea ref="fontArea" /> <FontArea ref="fontArea" />
@@ -303,11 +323,7 @@
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.computedAttribute') }} >{{ $t('cmdb.ciType.computedAttribute') }}
<a-tooltip <a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
:title="
$t('cmdb.ciType.computedAttributeTips')
"
>
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle" type="question-circle"
@@ -355,6 +371,8 @@ import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import vueJsonEditor from 'vue-json-editor' import vueJsonEditor from 'vue-json-editor'
import { import {
// createAttribute,
// createCITypeAttributes,
updateAttributeById, updateAttributeById,
updateCITypeAttributesById, updateCITypeAttributesById,
canDefineComputed, canDefineComputed,
@@ -364,10 +382,11 @@ import { valueTypeMap } from '../../utils/const'
import ComputedArea from './computedArea.vue' import ComputedArea from './computedArea.vue'
import PreValueArea from './preValueArea.vue' import PreValueArea from './preValueArea.vue'
import FontArea from './fontArea.vue' import FontArea from './fontArea.vue'
import RegSelect from '@/components/RegexSelect'
export default { export default {
name: 'AttributeEditForm', name: 'AttributeEditForm',
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea }, components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect },
props: { props: {
CITypeId: { CITypeId: {
type: Number, type: Number,
@@ -395,6 +414,7 @@ export default {
isShowComputedArea: false, isShowComputedArea: false,
defaultForDatetime: '', defaultForDatetime: '',
re_check: {},
} }
}, },
@@ -517,15 +537,30 @@ export default {
}) })
} }
console.log(_record) console.log(_record)
if (!['6'].includes(_record.value_type) && _record.re_check) {
this.re_check = {
value: _record.re_check,
}
} else {
this.re_check = {}
}
if (_record.default) { if (_record.default) {
this.$nextTick(() => { this.$nextTick(() => {
if (_record.value_type === '0') { if (_record.value_type === '0') {
this.form.setFieldsValue({ if (_record.is_list) {
default_value: _record.default.default ? [_record.default.default] : [], this.$nextTick(() => {
}) this.form.setFieldsValue({
default_value: _record.default.default ? _record.default.default : '',
})
})
} else {
this.form.setFieldsValue({
default_value: _record.default.default ? [_record.default.default] : [],
})
}
} else if (_record.value_type === '6') { } else if (_record.value_type === '6') {
this.default_value_json = _record?.default?.default || null this.default_value_json = _record?.default?.default || null
} else if (_record.value_type === '3' || _record.value_type === '4') { } else if ((_record.value_type === '3' || _record.value_type === '4') && !_record.is_list) {
if (_record?.default?.default === '$created_at' || _record?.default?.default === '$updated_at') { if (_record?.default?.default === '$created_at' || _record?.default?.default === '$updated_at') {
this.defaultForDatetime = _record.default.default this.defaultForDatetime = _record.default.default
this.form.setFieldsValue({ this.form.setFieldsValue({
@@ -584,6 +619,9 @@ export default {
await this.form.validateFields(async (err, values) => { await this.form.validateFields(async (err, values) => {
if (!err) { if (!err) {
console.log('Received values of form: ', values) console.log('Received values of form: ', values)
// if (values.choice_value) {
// values.choice_value = values.choice_value.split('\n')
// }
if (this.record.is_required !== values.is_required || this.record.default_show !== values.default_show) { if (this.record.is_required !== values.is_required || this.record.default_show !== values.default_show) {
console.log('changed is_required') console.log('changed is_required')
@@ -598,7 +636,11 @@ export default {
delete values['is_required'] delete values['is_required']
const { default_value } = values const { default_value } = values
if (values.value_type === '0' && default_value) { if (values.value_type === '0' && default_value) {
values.default = { default: default_value[0] || null } if (values.is_list) {
values.default = { default: default_value || null }
} else {
values.default = { default: default_value[0] || null }
}
} else if (values.value_type === '6') { } else if (values.value_type === '6') {
if (this.default_value_json_right) { if (this.default_value_json_right) {
values.default = { default: this.default_value_json } values.default = { default: this.default_value_json }
@@ -606,13 +648,13 @@ export default {
values.default = { default: null } values.default = { default: null }
} }
} else if (default_value || default_value === 0) { } else if (default_value || default_value === 0) {
if (values.value_type === '3') { if (values.value_type === '3' && !values.is_list) {
if (default_value === '$created_at' || default_value === '$updated_at') { if (default_value === '$created_at' || default_value === '$updated_at') {
values.default = { default: default_value } values.default = { default: default_value }
} else { } else {
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') } values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
} }
} else if (values.value_type === '4') { } else if (values.value_type === '4' && !values.is_list) {
values.default = { default: moment(default_value).format('YYYY-MM-DD') } values.default = { default: moment(default_value).format('YYYY-MM-DD') }
} else { } else {
values.default = { default: default_value } values.default = { default: default_value }
@@ -641,6 +683,9 @@ export default {
values.value_type = '2' values.value_type = '2'
values.is_link = true values.is_link = true
} }
if (values.value_type !== '6') {
values.re_check = this.re_check?.value ?? null
}
if (values.id) { if (values.id) {
await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed) await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed)
} else { } else {
@@ -698,6 +743,21 @@ export default {
async handleCalcComputed() { async handleCalcComputed() {
await this.handleSubmit(true) await this.handleSubmit(true)
}, },
getLimitedFormat() {
if (['0'].includes(this.currentValueType)) {
return ['number', 'phone', 'landline', 'zipCode', 'IDCard', 'monetaryAmount', 'custom']
}
if (['1'].includes(this.currentValueType)) {
return ['number', 'monetaryAmount', 'custom']
}
if (['3', '4', '5'].includes(this.currentValueType)) {
return ['custom']
}
if (this.currentValueType === '8') {
return ['link', 'custom']
}
return []
},
}, },
watch: {}, watch: {},
} }

View File

@@ -21,7 +21,10 @@
message: $t('cmdb.ciType.attributeNameTips'), message: $t('cmdb.ciType.attributeNameTips'),
pattern: RegExp('^(?!\\d)[a-zA-Z_0-9]+$'), pattern: RegExp('^(?!\\d)[a-zA-Z_0-9]+$'),
}, },
{ message: $t('cmdb.ciType.buildinAttribute'), pattern: RegExp('^(?!(id|_id|ci_id|type|_type|ci_type)$).*$') }, {
message: $t('cmdb.ciType.buildinAttribute'),
pattern: RegExp('^(?!(id|_id|ci_id|type|_type|ci_type)$).*$'),
},
], ],
}, },
]" ]"
@@ -59,13 +62,12 @@
:label="$t('cmdb.ciType.defaultValue')" :label="$t('cmdb.ciType.defaultValue')"
> >
<template> <template>
<a-select <a-input
v-if="form.getFieldValue('is_list')" v-if="form.getFieldValue('is_list')"
mode="tags"
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-select> </a-input>
<a-input-number <a-input-number
style="width: 100%" style="width: 100%"
v-else-if="currentValueType === '1'" v-else-if="currentValueType === '1'"
@@ -162,7 +164,11 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item :label-col="{ span: 8 }" :wrapper-col="horizontalFormItemLayout.wrapperCol" :label="$t('cmdb.ciType.unique')"> <a-form-item
:label-col="{ span: 8 }"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')"
>
<a-switch <a-switch
:disabled="isShowComputedArea" :disabled="isShowComputedArea"
@change="onChange" @change="onChange"
@@ -280,6 +286,11 @@
</a-col> </a-col>
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
<a-row> <a-row>
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" />
</a-form-item>
</a-col>
<a-col :span="24"> <a-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
<FontArea ref="fontArea" /> <FontArea ref="fontArea" />
@@ -296,11 +307,7 @@
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.computedAttribute') }} >{{ $t('cmdb.ciType.computedAttribute') }}
<a-tooltip <a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
:title="
$t('cmdb.ciType.computedAttributeTips')
"
>
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle" type="question-circle"
@@ -340,6 +347,7 @@ import { valueTypeMap } from '../../utils/const'
import ComputedArea from './computedArea.vue' import ComputedArea from './computedArea.vue'
import PreValueArea from './preValueArea.vue' import PreValueArea from './preValueArea.vue'
import FontArea from './fontArea.vue' import FontArea from './fontArea.vue'
import RegSelect from '@/components/RegexSelect'
export default { export default {
name: 'CreateNewAttribute', name: 'CreateNewAttribute',
@@ -348,6 +356,7 @@ export default {
PreValueArea, PreValueArea,
vueJsonEditor, vueJsonEditor,
FontArea, FontArea,
RegSelect,
}, },
props: { props: {
hasFooter: { hasFooter: {
@@ -374,6 +383,8 @@ export default {
isShowComputedArea: false, isShowComputedArea: false,
defaultForDatetime: '', defaultForDatetime: '',
re_check: {},
} }
}, },
computed: { computed: {
@@ -397,8 +408,12 @@ export default {
const data = { is_required, default_show } const data = { is_required, default_show }
delete values.is_required delete values.is_required
delete values.default_show delete values.default_show
if (values.value_type === '0' && default_value && default_value.length) { if (values.value_type === '0' && default_value) {
values.default = { default: default_value[0] } if (values.is_list) {
values.default = { default: default_value || null }
} else {
values.default = { default: default_value[0] || null }
}
} else if (values.value_type === '6') { } else if (values.value_type === '6') {
if (this.default_value_json_right) { if (this.default_value_json_right) {
values.default = { default: this.default_value_json } values.default = { default: this.default_value_json }
@@ -406,13 +421,13 @@ export default {
values.default = { default: null } values.default = { default: null }
} }
} else if (default_value || default_value === 0) { } else if (default_value || default_value === 0) {
if (values.value_type === '3') { if (values.value_type === '3' && !values.is_list) {
if (default_value === '$created_at' || default_value === '$updated_at') { if (default_value === '$created_at' || default_value === '$updated_at') {
values.default = { default: default_value } values.default = { default: default_value }
} else { } else {
values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') } values.default = { default: moment(default_value).format('YYYY-MM-DD HH:mm:ss') }
} }
} else if (values.value_type === '4') { } else if (values.value_type === '4' && !values.is_list) {
values.default = { default: moment(default_value).format('YYYY-MM-DD') } values.default = { default: moment(default_value).format('YYYY-MM-DD') }
} else { } else {
values.default = { default: default_value } values.default = { default: default_value }
@@ -449,6 +464,9 @@ export default {
values.value_type = '2' values.value_type = '2'
values.is_link = true values.is_link = true
} }
if (values.value_type !== '6') {
values.re_check = this.re_check?.value ?? null
}
const { attr_id } = await createAttribute({ ...values, option: { fontOptions } }) const { attr_id } = await createAttribute({ ...values, option: { fontOptions } })
this.form.resetFields() this.form.resetFields()
@@ -539,6 +557,21 @@ export default {
default_value: key, default_value: key,
}) })
}, },
getLimitedFormat() {
if (['0'].includes(this.currentValueType)) {
return ['number', 'phone', 'landline', 'zipCode', 'IDCard', 'monetaryAmount', 'custom']
}
if (['1'].includes(this.currentValueType)) {
return ['number', 'monetaryAmount', 'custom']
}
if (['3', '4', '5'].includes(this.currentValueType)) {
return ['custom']
}
if (this.currentValueType === '8') {
return ['link', 'custom']
}
return []
},
}, },
} }
</script> </script>

View File

@@ -37,7 +37,7 @@
>{{ $t('cmdb.ciType.attributeLibray') }}</a >{{ $t('cmdb.ciType.attributeLibray') }}</a
> >
<a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"> <a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')">
<a><ops-icon type="ops-menu" /></a> <a><ops-icon type="ops-menu"/></a>
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item key="0"> <a-menu-item key="0">
<a-upload <a-upload
@@ -48,12 +48,15 @@
action="/api/v0.1/ci_types/template/import/file" action="/api/v0.1/ci_types/template/import/file"
@change="changeUploadFile" @change="changeUploadFile"
> >
<a><a-icon type="upload" /></a><a> {{ $t('upload') }}</a> <a><a-icon type="upload"/></a><a> {{ $t('upload') }}</a>
</a-upload> </a-upload>
</a-menu-item> </a-menu-item>
<a-menu-item key="1"> <a-menu-item key="1">
<a-space> <a-space>
<a href="/api/v0.1/ci_types/template/export/file"><a-icon type="download" /> {{ $t('download') }}</a> <a
href="/api/v0.1/ci_types/template/export/file"
><a-icon type="download" /> {{ $t('download') }}</a
>
</a-space> </a-space>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@@ -63,9 +66,11 @@
<draggable class="ci-types-left-content" :list="CITypeGroups" @end="handleChangeGroups" filter=".undraggable"> <draggable class="ci-types-left-content" :list="CITypeGroups" @end="handleChangeGroups" filter=".undraggable">
<div v-for="g in CITypeGroups" :key="g.id || g.name"> <div v-for="g in CITypeGroups" :key="g.id || g.name">
<div <div
:class="`${currentGId === g.id && !currentCId ? 'selected' : ''} ci-types-left-group ${ :class="
g.id === -1 ? 'undraggable' : '' `${currentGId === g.id && !currentCId ? 'selected' : ''} ci-types-left-group ${
}`" g.id === -1 ? 'undraggable' : ''
}`
"
@click="handleClickGroup(g.id)" @click="handleClickGroup(g.id)"
> >
<div> <div>
@@ -78,16 +83,16 @@
<a-space> <a-space>
<a-tooltip> <a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template> <template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template>
<a><a-icon type="plus" @click="handleCreate(g)" /></a> <a><a-icon type="plus" @click="handleCreate(g)"/></a>
</a-tooltip> </a-tooltip>
<template v-if="g.id !== -1"> <template v-if="g.id !== -1">
<a-tooltip> <a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.editGroup') }}</template> <template slot="title">{{ $t('cmdb.ciType.editGroup') }}</template>
<a><a-icon type="edit" @click="handleEditGroup(g)" /></a> <a><a-icon type="edit" @click="handleEditGroup(g)"/></a>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template> <template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
<a style="color: red"><a-icon type="delete" @click="handleDeleteGroup(g)" /></a> <a style="color: red"><a-icon type="delete" @click="handleDeleteGroup(g)"/></a>
</a-tooltip> </a-tooltip>
</template> </template>
</a-space> </a-space>
@@ -130,14 +135,15 @@
</div> </div>
<span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span> <span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span>
<a-space class="ci-types-left-detail-action"> <a-space class="ci-types-left-detail-action">
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)" /></a> <a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a>
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)" /></a> <a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
<a <a
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')" v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
@click="(e) => handleDownloadCiType(e, ci)"> @click="(e) => handleDownloadCiType(e, ci)"
<a-icon type="download"/> >
<a-icon type="download" />
</a> </a>
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete" /></a> <a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a>
</a-space> </a-space>
</div> </div>
</draggable> </draggable>
@@ -242,6 +248,11 @@
:filter-method="filterOption" :filter-method="filterOption"
v-decorator="['unique_key', { rules: [{ required: true, message: $t('cmdb.ciType.uniqueKeySelect') }] }]" v-decorator="['unique_key', { rules: [{ required: true, message: $t('cmdb.ciType.uniqueKeySelect') }] }]"
:placeholder="$t('placeholder2')" :placeholder="$t('placeholder2')"
@visible-change="
() => {
filterInput = ''
}
"
> >
<el-option <el-option
:key="item.id" :key="item.id"
@@ -549,6 +560,7 @@ export default {
}) })
}, },
onClose() { onClose() {
this.filterInput = ''
this.form.resetFields() this.form.resetFields()
this.drawerVisible = false this.drawerVisible = false
}, },
@@ -739,7 +751,7 @@ export default {
const x = new XMLHttpRequest() const x = new XMLHttpRequest()
x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true) x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true)
x.responseType = 'blob' x.responseType = 'blob'
x.onload = function (e) { x.onload = function(e) {
const url = window.URL.createObjectURL(x.response) const url = window.URL.createObjectURL(x.response)
const a = document.createElement('a') const a = document.createElement('a')
a.href = url a.href = url

View File

@@ -79,7 +79,7 @@
<vxe-column <vxe-column
align="center" align="center"
field="is_accept" field="is_accept"
:title="$t('cmdb.ad.isAccpet')" :title="$t('cmdb.ad.isAccept')"
v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }" v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
:filters="[ :filters="[
{ label: $t('yes'), value: true }, { label: $t('yes'), value: true },
@@ -92,7 +92,7 @@
</vxe-column> </vxe-column>
<vxe-column <vxe-column
field="accept_by" field="accept_by"
:title="$t('cmdb.ad.accpetBy')" :title="$t('cmdb.ad.acceptBy')"
v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }" v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }"
:filters="[]" :filters="[]"
></vxe-column> ></vxe-column>
@@ -186,8 +186,8 @@ export default {
this.clickSidebar(Number(_currentType)) this.clickSidebar(Number(_currentType))
return return
} }
if (res && res.length) { if (res && res.length && res[0].ci_types && res[0].ci_types.length) {
this.clickSidebar(res[0].id) this.clickSidebar(res[0].ci_types[0].id)
} }
}) })
}, },
@@ -246,7 +246,7 @@ export default {
content: that.$t('cmdb.ad.confirmAccept'), content: that.$t('cmdb.ad.confirmAccept'),
onOk() { onOk() {
updateADCAccept(row.id).then(() => { updateADCAccept(row.id).then(() => {
that.$message.success(that.$t('cmdb.ad.accpetSuccess')) that.$message.success(that.$t('cmdb.ad.acceptSuccess'))
that.getAdc(false) that.getAdc(false)
}) })
}, },

View File

@@ -102,7 +102,7 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import Pager from './pager.vue' import Pager from '@/components/Pager'
import SearchForm from './searchForm.vue' import SearchForm from './searchForm.vue'
import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history' import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypes } from '@/modules/cmdb/api/CIType'

View File

@@ -1,116 +0,0 @@
<template>
<div>
<a-row class="row" type="flex" justify="end">
<a-col>
<a-space align="end">
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button>
<a-button class="page-button" size="small" >{{ currentPage }}</a-button>
<a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button>
<a-dropdown class="dropdown" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
<a-menu slot="overlay">
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
{{ size }}{{ $t('cmdb.history.itemsPerPage') }}
</a-menu-item>
</a-menu>
<a-button size="small"> {{ pageSize }}{{ $t('cmdb.history.itemsPerPage') }} <a-icon type="down" /> </a-button>
</a-dropdown>
</a-space>
</a-col>
</a-row>
</div>
</template>
<script>
export default {
props: {
currentPage: {
type: Number,
required: true
},
pageSize: {
type: Number,
required: true
},
pageSizes: {
type: Array,
required: true
},
total: {
type: Number,
required: true
},
isLoading: {
type: Boolean,
required: false
}
},
data() {
return {
dropdownIsDisabled: false,
prevIsDisabled: true,
}
},
computed: {
nextIsDisabled() {
return this.isLoading || this.total < this.pageSize
}
},
watch: {
isLoading: {
immediate: true,
handler: function (val) {
if (val === true) {
this.dropdownIsDisabled = true
this.prevIsDisabled = true
} else {
this.dropdownIsDisabled = false
if (this.currentPage === 1) {
this.prevIsDisabled = true
} else {
this.prevIsDisabled = false
}
}
}
},
currentPage: {
immediate: true,
handler: function (val) {
if (val === 1) {
this.prevIsDisabled = true
}
}
}
},
methods: {
handleItemClick(size) {
this.$emit('showSizeChange', size)
},
nextPage() {
const pageNum = this.currentPage + 1
this.$emit('change', pageNum)
},
prevPage() {
const pageNum = this.currentPage - 1
this.$emit('change', pageNum)
}
}
}
</script>
<style lang="less" scoped>
.row{
margin-top: 5px;
.left-button{
padding: 0;
width: 24px;
}
.right-button{
padding: 0;
width: 24px;
}
.page-button{
padding: 0;
width: 24px;
}
}
</style>

View File

@@ -134,7 +134,7 @@
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import SearchForm from './searchForm' import SearchForm from './searchForm'
import Pager from './pager.vue' import Pager from '@/components/Pager'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypes } from '@/modules/cmdb/api/CIType'
import { getRelationTable, getUsers } from '@/modules/cmdb/api/history' import { getRelationTable, getUsers } from '@/modules/cmdb/api/history'
import { getRelationTypes } from '@/modules/cmdb/api/relationType' import { getRelationTypes } from '@/modules/cmdb/api/relationType'

View File

@@ -5,7 +5,7 @@
<span class="cmdb-preference-left-card-title">{{ $t('cmdb.preference.mySub') }}</span> <span class="cmdb-preference-left-card-title">{{ $t('cmdb.preference.mySub') }}</span>
<span <span
class="cmdb-preference-left-card-content" class="cmdb-preference-left-card-content"
><ops-icon type="cmdb-ci" :style="{ marginRight: '5px' }" />{{ $t('cmdb.menu.ciTable') }}: ><ops-icon type="cmdb-ci" :style="{ marginRight: '5px' }"/>{{ $t('cmdb.menu.ciTable') }}:
<a-badge <a-badge
showZero showZero
:count="self.instance.length" :count="self.instance.length"
@@ -16,11 +16,10 @@
height: '23px', height: '23px',
fontSize: '14px', fontSize: '14px',
}" }"
/></span /></span>
>
<span <span
class="cmdb-preference-left-card-content" class="cmdb-preference-left-card-content"
><ops-icon type="cmdb-tree" :style="{ marginRight: '5px' }" />{{ $t('cmdb.menu.ciTree') }}: ><ops-icon type="cmdb-tree" :style="{ marginRight: '5px' }"/>{{ $t('cmdb.menu.ciTree') }}:
<a-badge <a-badge
showZero showZero
:count="self.tree.length" :count="self.tree.length"
@@ -31,57 +30,67 @@
height: '23px', height: '23px',
fontSize: '14px', fontSize: '14px',
}" }"
/></span /></span>
>
</div> </div>
<div class="cmdb-preference-group" v-for="(group, index) in myPreferences" :key="group.name"> <div class="cmdb-preference-group" v-for="(group, index) in myPreferences" :key="group.name">
<div class="cmdb-preference-group-title"> <div class="cmdb-preference-group-title">
<span> <ops-icon :style="{ marginRight: '10px' }" :type="group.icon" />{{ group.name }} </span> <span> <ops-icon :style="{ marginRight: '10px' }" :type="group.icon" />{{ group.name }} </span>
</div> </div>
<div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id"> <draggable
<div v-model="group.ci_types"
:class="{ :animation="300"
'cmdb-preference-avatar': true, @change="
'cmdb-preference-avatar-noicon': !ciType.icon, (e) => {
'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed, orderChange(e, group)
}" }
:style="{ width: '30px', height: '30px', marginRight: '10px' }" "
> >
<template v-if="ciType.icon"> <div class="cmdb-preference-group-content" v-for="ciType in group.ci_types" :key="ciType.id">
<img <OpsMoveIcon class="cmdb-preference-move-icon" />
v-if="ciType.icon.split('$$')[2]" <div
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`" :class="{
:style="{ maxHeight: '30px', maxWidth: '30px' }" 'cmdb-preference-avatar': true,
/> 'cmdb-preference-avatar-noicon': !ciType.icon,
<ops-icon 'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed,
v-else }"
:style="{ :style="{ width: '30px', height: '30px', marginRight: '10px' }"
color: ciType.icon.split('$$')[1], >
fontSize: '14px', <template v-if="ciType.icon">
}" <img
:type="ciType.icon.split('$$')[0]" v-if="ciType.icon.split('$$')[2]"
/> :src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
</template> :style="{ maxHeight: '30px', maxWidth: '30px' }"
<span v-else :style="{ fontSize: '20px' }">{{ ciType.name[0].toUpperCase() }}</span> />
<ops-icon
v-else
:style="{
color: ciType.icon.split('$$')[1],
fontSize: '14px',
}"
:type="ciType.icon.split('$$')[0]"
/>
</template>
<span v-else :style="{ fontSize: '20px' }">{{ ciType.name[0].toUpperCase() }}</span>
</div>
<span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span>
<span class="cmdb-preference-group-content-action">
<a-tooltip :title="$t('cmdb.preference.cancelSub')">
<span
@click="unsubscribe(ciType, group.type)"
><ops-icon type="cmdb-preference-cancel-subscribe" />
</span>
</a-tooltip>
<a-divider type="vertical" :style="{ margin: '0 3px' }" />
<a-tooltip :title="$t('cmdb.preference.editSub')">
<span
@click="openSubscribeSetting(ciType, `${index + 1}`)"
><ops-icon
type="cmdb-preference-subscribe"
/></span>
</a-tooltip>
</span>
</div> </div>
<span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span> </draggable>
<span class="cmdb-preference-group-content-action">
<a-tooltip :title="$t('cmdb.preference.cancelSub')">
<span
@click="unsubscribe(ciType, group.type)"
><ops-icon type="cmdb-preference-cancel-subscribe" />
</span>
</a-tooltip>
<a-divider type="vertical" :style="{ margin: '0 3px' }" />
<a-tooltip :title="$t('cmdb.preference.editSub')">
<span
@click="openSubscribeSetting(ciType, `${index + 1}`)"
><ops-icon
type="cmdb-preference-subscribe"
/></span>
</a-tooltip>
</span>
</div>
</div> </div>
</div> </div>
<div class="cmdb-preference-right"> <div class="cmdb-preference-right">
@@ -124,19 +133,12 @@
> >
</div> </div>
<div class="cmdb-preference-colleague"> <div class="cmdb-preference-colleague">
<template v-if="type_id2users[item.id] && type_id2users[item.id].length"> <span
<span v-if="type_id2users[item.id] && type_id2users[item.id].length"
>{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length >{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length
}}{{ $t('cmdb.preference.peopleSub') }}</span }}{{ $t('cmdb.preference.peopleSub') }}</span
> >
<span class="cmdb-preference-colleague-name"> <span v-else>{{ $t('cmdb.preference.noSub') }}</span>
<span v-for="uid in type_id2users[item.id].slice(0, 4)" :key="uid">
{{ getNameByUid(uid) }}
</span>
<span class="cmdb-preference-colleague-ellipsis" v-if="type_id2users[item.id].length > 4">...</span>
</span>
</template>
<span v-else :style="{ marginLeft: 'auto' }">{{ $t('cmdb.preference.noSub') }}</span>
</div> </div>
<div class="cmdb-preference-progress"> <div class="cmdb-preference-progress">
<div class="cmdb-preference-progress-info"> <div class="cmdb-preference-progress-info">
@@ -190,15 +192,23 @@ import router, { resetRouter } from '@/router'
import store from '@/store' import store from '@/store'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import moment from 'moment' import moment from 'moment'
import draggable from 'vuedraggable'
import { getCITypeGroups } from '../../api/ciTypeGroup' import { getCITypeGroups } from '../../api/ciTypeGroup'
import { getPreference, getPreference2, subscribeCIType, subscribeTreeView } from '@/modules/cmdb/api/preference' import {
getPreference,
getPreference2,
subscribeCIType,
subscribeTreeView,
preferenceCitypeOrder,
} from '@/modules/cmdb/api/preference'
import CollapseTransition from '@/components/CollapseTransition' import CollapseTransition from '@/components/CollapseTransition'
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting' import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
import { getCIAdcStatistics } from '../../api/ci' import { getCIAdcStatistics } from '../../api/ci'
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
export default { export default {
name: 'Preference', name: 'Preference',
components: { CollapseTransition, SubscribeSetting }, components: { CollapseTransition, SubscribeSetting, draggable, OpsMoveIcon },
data() { data() {
return { return {
citypeData: [], citypeData: [],
@@ -214,7 +224,6 @@ export default {
computed: { computed: {
...mapState({ ...mapState({
windowHeight: (state) => state.windowHeight, windowHeight: (state) => state.windowHeight,
allUsers: (state) => state.user.allUsers,
}), }),
}, },
mounted() { mounted() {
@@ -277,10 +286,6 @@ export default {
}, 300) }, 300)
} }
}, },
getNameByUid(uid) {
const _find = this.allUsers.find((item) => item.uid === uid)
return _find?.username[0].toUpperCase() || 'A'
},
getsubscribedDays(item) { getsubscribedDays(item) {
const subscribedTime = this.self.type_id2subs_time[item.id] const subscribedTime = this.self.type_id2subs_time[item.id]
moment.duration(moment().diff(moment(subscribedTime))) moment.duration(moment().diff(moment(subscribedTime)))
@@ -351,6 +356,17 @@ export default {
this.expandKeys.push(group.id) this.expandKeys.push(group.id)
} }
}, },
orderChange(e, group) {
preferenceCitypeOrder({ type_ids: group.ci_types.map((type) => type.id), is_tree: group.type !== 'ci' })
.then(() => {
if (group.type === 'ci') {
this.resetRoute()
}
})
.catch(() => {
this.getCITypes(false)
})
},
}, },
} }
</script> </script>
@@ -426,7 +442,7 @@ export default {
align-items: center; align-items: center;
height: 45px; height: 45px;
padding: 0 8px; padding: 0 8px;
cursor: default; cursor: move;
justify-content: flex-start; justify-content: flex-start;
&:hover { &:hover {
background: #ffffff; background: #ffffff;
@@ -437,6 +453,15 @@ export default {
white-space: nowrap; white-space: nowrap;
margin-left: auto; margin-left: auto;
} }
.cmdb-preference-move-icon {
visibility: visible;
}
}
.cmdb-preference-move-icon {
width: 14px;
height: 20px;
cursor: move;
visibility: hidden;
} }
.cmdb-preference-group-content-title { .cmdb-preference-group-content-title {
flex: 1; flex: 1;

View File

@@ -58,13 +58,9 @@
/> />
<div class="relation-views-right-bar"> <div class="relation-views-right-bar">
<a-space> <a-space>
<a-button <a-button v-if="isLeaf" type="primary" size="small" @click="$refs.create.handleOpen(true, 'create')">{{
v-if="isLeaf" $t('create')
type="primary" }}</a-button>
size="small"
@click="$refs.create.handleOpen(true, 'create')"
>{{ $t('create') }}</a-button
>
<div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon"> <div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon">
<template v-if="selectedRowKeys.length"> <template v-if="selectedRowKeys.length">
@@ -135,7 +131,7 @@
{{ col.title }}</span {{ col.title }}</span
> >
</template> </template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }"> <template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
@@ -172,18 +168,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select
:getPopupContainer="(trigger) => trigger.parentElement"
:style="{ width: '100%', height: '32px' }"
v-model="row[col.field]"
:placeholder="$t('placeholder2')"
v-else-if="col.is_list"
:showArrow="false"
mode="tags"
class="ci-table-edit-select"
allowClear
>
</a-select>
</template> </template>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -364,7 +348,7 @@ import {
} from '@/modules/cmdb/api/CIRelation' } from '@/modules/cmdb/api/CIRelation'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { searchCI2, updateCI, deleteCI, searchCI } from '@/modules/cmdb/api/ci' import { searchCI2, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
import { getCITypes } from '../../api/CIType' import { getCITypes } from '../../api/CIType'
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission' import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
import { searchResourceType } from '@/modules/acl/api/resource' import { searchResourceType } from '@/modules/acl/api/resource'
@@ -1018,11 +1002,7 @@ export default {
this.$confirm({ this.$confirm({
title: that.$t('warning'), title: that.$t('warning'),
content: (h) => ( content: (h) => <div>{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}</div>,
<div>
{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}
</div>
),
onOk() { onOk() {
deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => { deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {
that.$message.success(that.$t('deleteSuccess')) that.$message.success(that.$t('deleteSuccess'))
@@ -1048,7 +1028,7 @@ export default {
title: that.$t('warning'), title: that.$t('warning'),
content: (h) => ( content: (h) => (
<div> <div>
{that.$t('cmdb.serviceTreedeleteRelationConfirm', { name: currentShowType.alias || currentShowType.name })} {that.$t('cmdb.serviceTree.deleteRelationConfirm', { name: currentShowType.alias || currentShowType.name })}
</div> </div>
), ),
onOk() { onOk() {

View File

@@ -32,32 +32,24 @@
> >
<template #one> <template #one>
<div class="tree-views-left" :style="{ height: `${windowHeight - 115}px` }"> <div class="tree-views-left" :style="{ height: `${windowHeight - 115}px` }">
<a-collapse <draggable
:activeKey="current" v-model="subscribeTreeViewCiTypes"
accordion :animation="300"
@change="handleChangeCi" @change="
:bordered="false" (e) => {
:destroyInactivePanel="true" orderChange(e, subscribeTreeViewCiTypes)
}
"
> >
<a-collapse-panel <div v-for="ciType in subscribeTreeViewCiTypes" :key="ciType.type_id">
v-for="ciType in subscribeTreeViewCiTypes"
:key="String(ciType.type_id)"
:showArrow="false"
:style="{
borderRadius: '4px',
marginBottom: '5px',
border: 0,
overflow: 'hidden',
width: '100%',
}"
>
<div <div
slot="header" @click="handleChangeCi(ciType.type_id)"
:class="{ :class="{
'custom-header': true, 'custom-header': true,
'custom-header-selected': Number(ciType.type_id) === Number(typeId), 'custom-header-selected': Number(ciType.type_id) === Number(typeId) && !treeKeys.length,
}" }"
> >
<OpsMoveIcon class="move-icon" />
<span class="tree-views-left-header-icon"> <span class="tree-views-left-header-icon">
<template v-if="ciType.icon"> <template v-if="ciType.icon">
<img <img
@@ -95,6 +87,7 @@
:tree-data="treeData" :tree-data="treeData"
:load-data="onLoadData" :load-data="onLoadData"
:expandedKeys="expandedKeys" :expandedKeys="expandedKeys"
v-if="Number(ciType.type_id) === Number(typeId)"
> >
<a-icon slot="switcherIcon" type="down" /> <a-icon slot="switcherIcon" type="down" />
<template #title="{ key: treeKey, title, isLeaf }"> <template #title="{ key: treeKey, title, isLeaf }">
@@ -107,8 +100,8 @@
/> />
</template> </template>
</a-tree> </a-tree>
</a-collapse-panel> </div>
</a-collapse> </draggable>
</div> </div>
</template> </template>
<template #two> <template #two>
@@ -189,7 +182,7 @@
{{ col.title }}</span {{ col.title }}</span
> >
</template> </template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }"> <template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
@@ -226,18 +219,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select
:getPopupContainer="(trigger) => trigger.parentElement"
:style="{ width: '100%', height: '32px' }"
v-model="row[col.field]"
:placeholder="$t('placeholder2')"
v-else-if="col.is_list"
:showArrow="false"
mode="tags"
class="ci-table-edit-select"
allowClear
>
</a-select>
</template> </template>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -407,7 +388,13 @@
/* eslint-disable no-useless-escape */ /* eslint-disable no-useless-escape */
import _ from 'lodash' import _ from 'lodash'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import { getSubscribeTreeView, getSubscribeAttributes, subscribeTreeView } from '@/modules/cmdb/api/preference' import draggable from 'vuedraggable'
import {
getSubscribeTreeView,
getSubscribeAttributes,
subscribeTreeView,
preferenceCitypeOrder,
} from '@/modules/cmdb/api/preference'
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci' import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypes } from '@/modules/cmdb/api/CIType'
import { getCITableColumns } from '../../utils/helper' import { getCITableColumns } from '../../utils/helper'
@@ -444,6 +431,7 @@ export default {
PreferenceSearch, PreferenceSearch,
MetadataDrawer, MetadataDrawer,
OpsMoveIcon, OpsMoveIcon,
draggable,
}, },
data() { data() {
return { return {
@@ -463,7 +451,6 @@ export default {
pageSize: 50, pageSize: 50,
currentPage: 1, currentPage: 1,
totalNumber: 0, totalNumber: 0,
current: '', // 当前页面的type_id
currentAttrList: [], currentAttrList: [],
trigger: false, trigger: false,
newLoad: true, newLoad: true,
@@ -571,7 +558,6 @@ export default {
this.subscribeTreeViewCiTypes = res this.subscribeTreeViewCiTypes = res
if (this.subscribeTreeViewCiTypes.length) { if (this.subscribeTreeViewCiTypes.length) {
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
this.current = `${this.typeId}`
this.selectedRowKeys = [] this.selectedRowKeys = []
this.$refs.xTable.getVxetableRef().clearCheckboxRow() this.$refs.xTable.getVxetableRef().clearCheckboxRow()
this.$refs.xTable.getVxetableRef().clearCheckboxReserve() this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
@@ -600,7 +586,6 @@ export default {
async loadCurrentView() { async loadCurrentView() {
if (this.subscribeTreeViewCiTypes.length) { if (this.subscribeTreeViewCiTypes.length) {
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
this.current = String(this.typeId)
this.selectedRowKeys = [] this.selectedRowKeys = []
this.$refs.xTable.getVxetableRef().clearCheckboxRow() this.$refs.xTable.getVxetableRef().clearCheckboxRow()
this.$refs.xTable.getVxetableRef().clearCheckboxReserve() this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
@@ -746,9 +731,14 @@ export default {
name: 'cmdb_tree_views_item', name: 'cmdb_tree_views_item',
params: { typeId: Number(value) }, params: { typeId: Number(value) },
}) })
this.typeId = Number(value)
} else { } else {
this.newLoad = true this.typeId = null
this.initPage() this.$nextTick(() => {
this.typeId = Number(value)
this.newLoad = true
this.initPage()
})
} }
this.isSetDataNodes = [] this.isSetDataNodes = []
}, },
@@ -1136,12 +1126,22 @@ export default {
} }
}) })
this.$refs.create.visible = false this.$refs.create.visible = false
const key = 'updatable'
let errorMsg = ''
for (let i = 0; i < this.selectedRowKeys.length; i++) { for (let i = 0; i < this.selectedRowKeys.length; i++) {
await updateCI(this.selectedRowKeys[i], payload, false) await updateCI(this.selectedRowKeys[i], payload, false)
.then(() => { .then(() => {
successNum += 1 successNum += 1
}) })
.catch(() => { .catch((error) => {
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
this.$notification.warning({
key,
message: this.$t('warning'),
description: errorMsg,
duration: 0,
style: { whiteSpace: 'break-spaces' },
})
errorNum += 1 errorNum += 1
}) })
.finally(() => { .finally(() => {
@@ -1209,6 +1209,13 @@ export default {
this.$message.error(this.$t('cmdb.ci.copyFailed')) this.$message.error(this.$t('cmdb.ci.copyFailed'))
}) })
}, },
orderChange(e, subscribeTreeViewCiTypes) {
preferenceCitypeOrder({ type_ids: subscribeTreeViewCiTypes.map((type) => type.type_id), is_tree: true }).catch(
() => {
this.getTreeViews()
}
)
},
}, },
} }
</script> </script>
@@ -1230,65 +1237,68 @@ export default {
&:hover { &:hover {
overflow: auto; overflow: auto;
} }
.ant-collapse-borderless { .custom-header {
background-color: #fff; width: 100%;
} display: inline-flex;
.ant-collapse-item:has(.custom-header-selected):not(:has(.ant-tree-treenode-selected)) > .ant-collapse-header, flex-direction: row;
.ant-collapse-item-active:not(:has(.ant-tree-treenode-selected)) > .ant-collapse-header { flex-wrap: nowrap;
background-color: #d6e4ff; justify-content: flex-start;
} align-items: center;
.ant-collapse-header { padding: 8px 0 8px 12px;
padding: 8px 12px 4px; cursor: move;
border-radius: 2px;
position: relative;
&:hover { &:hover {
background-color: #f0f5ff; background-color: #f0f5ff;
> .actions,
> .move-icon {
display: inherit;
}
} }
&:hover > .custom-header > .actions { .move-icon {
display: inherit; width: 14px;
height: 20px;
cursor: move;
position: absolute;
display: none;
left: 0;
} }
.custom-header { .tree-views-left-header-icon {
width: 100%;
display: inline-flex; display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center; align-items: center;
.tree-views-left-header-icon { justify-content: center;
display: inline-flex; width: 20px;
align-items: center; height: 20px;
justify-content: center; border-radius: 2px;
width: 20px; box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
height: 20px; margin-right: 6px;
border-radius: 2px; background-color: #fff;
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2); }
margin-right: 6px; .tree-views-left-header-name {
background-color: #fff; flex: 1;
} font-weight: bold;
.tree-views-left-header-name { margin-left: 5px;
flex: 1; overflow: hidden;
font-weight: bold; text-overflow: ellipsis;
margin-left: 5px; white-space: nowrap;
overflow: hidden; }
text-overflow: ellipsis; .actions {
white-space: nowrap; display: none;
} margin-left: auto;
.actions { cursor: pointer;
display: none; }
margin-left: auto; .action {
} display: inline-block;
.action { width: 22px;
display: inline-block; text-align: center;
width: 22px; border-radius: 5px;
text-align: center; &:hover {
border-radius: 5px; background-color: #cacaca;
&:hover {
background-color: #cacaca;
}
} }
} }
} }
.custom-header-selected {
.ant-collapse > .ant-collapse-item > .ant-collapse-header { background-color: #d3e3fd !important;
white-space: nowrap;
} }
.ant-tree li { .ant-tree li {
padding: 2px 0; padding: 2px 0;

View File

@@ -12,13 +12,13 @@
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background-color: rgba(47, 122, 235, 0.2); background-color: @scrollbar-color;
background-clip: padding-box; background-clip: padding-box;
border-radius: 5px; border-radius: 5px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background-color: rgba(47, 122, 235, 0.2); background-color: @scrollbar-color;
} }
@import '~ant-design-vue/dist/antd.less'; @import '~ant-design-vue/dist/antd.less';
@@ -35,7 +35,7 @@ body {
} }
.ant-layout { .ant-layout {
background-color: #f0f5ff; background-color: #custom_colors() [color_2];
} }
.layout.ant-layout { .layout.ant-layout {
@@ -99,12 +99,8 @@ body {
height: @layout-header-icon-height; height: @layout-header-icon-height;
line-height: @layout-header-icon-height; line-height: @layout-header-icon-height;
display: inline-flex; display: inline-flex;
align-items: flex-end; align-items: center;
margin-right: 10px; margin-right: 10px;
&:hover {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
color: @layout-header-font-selected-color;
}
} }
.topmenu { .topmenu {
@@ -192,12 +188,6 @@ body {
color: @layout-header-font-color; color: @layout-header-font-color;
align-items: center; align-items: center;
border-radius: 4px; border-radius: 4px;
&:hover {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
color: @layout-header-font-selected-color;
}
.avatar { .avatar {
margin-right: 5px; margin-right: 5px;
color: @layout-header-font-color; color: @layout-header-font-color;
@@ -349,8 +339,6 @@ body {
} }
&.light { &.light {
background-color: #225686;
.header-index-wide { .header-index-wide {
.header-index-left { .header-index-left {
.logo { .logo {
@@ -425,7 +413,6 @@ body {
min-height: 100vh; min-height: 100vh;
.ant-layout-sider-children { .ant-layout-sider-children {
background: #225686; //浅色系左边菜单栏 深色系需删除
overflow-y: hidden; overflow-y: hidden;
> .ant-menu { > .ant-menu {
height: calc(100vh - 40px); height: calc(100vh - 40px);
@@ -519,7 +506,7 @@ body {
.ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { .ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
// background-image: url('../assets/sidebar_selected.png') !important; // background-image: url('../assets/sidebar_selected.png') !important;
// background-repeat: no-repeat !important; // background-repeat: no-repeat !important;
background: #3e4a71; background: @layout-sidebar-selected-color;
// background-size: 228px 38px; // background-size: 228px 38px;
// background-position-x: -10px; // background-position-x: -10px;
// background-position-y: center; // background-position-y: center;
@@ -546,13 +533,13 @@ body {
border-right-color: transparent; border-right-color: transparent;
// background: @layout-background-light-color; // background: @layout-background-light-color;
// background: url('../assets/sidebar_background.png'); // background: url('../assets/sidebar_background.png');
background: #1d264c; background: @layout-sidebar-color;
// background-position-x: center; // background-position-x: center;
// background-position-y: center; // background-position-y: center;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-size: cover; background-size: cover;
.ant-menu-inline.ant-menu-sub { .ant-menu-inline.ant-menu-sub {
background-color: #000c37; background-color: @layout-sidebar-sub-color;
} }
.ant-menu-submenu-content .ant-menu-item, .ant-menu-submenu-content .ant-menu-item,
.ant-menu-item { .ant-menu-item {
@@ -560,7 +547,7 @@ body {
> a { > a {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: rgba(255, 255, 255, 0.8); color: @layout-sidebar-font-color;
} }
&:hover { &:hover {
// background: #0000000a; // background: #0000000a;
@@ -578,7 +565,7 @@ body {
white-space: nowrap; white-space: nowrap;
} }
a:hover { a:hover {
color: #fff; color: @layout-sidebar-font-color;
font-weight: 600; font-weight: 600;
} }
&:hover .custom-menu-extra-ellipsis { &:hover .custom-menu-extra-ellipsis {
@@ -614,7 +601,7 @@ body {
.ant-menu-item-selected { .ant-menu-item-selected {
a, a,
a:hover { a:hover {
color: #fff; color: @layout-sidebar-font-color;
font-weight: 600; font-weight: 600;
} }
} }
@@ -624,20 +611,20 @@ body {
} }
.ant-menu-submenu { .ant-menu-submenu {
color: rgba(255, 255, 255, 0.8); color: @layout-sidebar-font-color;
} }
.ant-menu-submenu-title:hover { .ant-menu-submenu-title:hover {
color: #fff; color: @layout-sidebar-font-color;
background: #0000000a; background: @layout-sidebar-selected-color;
font-weight: 600;
.ant-menu-submenu-arrow::before, .ant-menu-submenu-arrow::before,
.ant-menu-submenu-arrow::after { .ant-menu-submenu-arrow::after {
background: #fff; background: @layout-sidebar-arrow-color;
background-image: linear-gradient(to right, #2e2e2e, #2e2e2e);
} }
} }
.ant-menu-submenu-selected { .ant-menu-submenu-selected {
> .ant-menu-submenu-title { > .ant-menu-submenu-title {
color: #fff; color: @layout-sidebar-font-color;
font-weight: 800; font-weight: 800;
} }
} }
@@ -650,7 +637,7 @@ body {
padding-left: 0 !important; padding-left: 0 !important;
> a { > a {
padding-left: 10px; padding-left: 10px;
color: rgba(255, 255, 255, 0.6) !important; color: @layout-sidebar-disabled-font-color !important;
font-size: 12px; font-size: 12px;
} }
&:hover { &:hover {
@@ -659,8 +646,11 @@ body {
} }
.ant-menu-submenu-arrow::after, .ant-menu-submenu-arrow::after,
.ant-menu-submenu-arrow::before { .ant-menu-submenu-arrow::before {
background: rgba(255, 255, 255, 0.6); background: @layout-sidebar-arrow-color;
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.6)) !important; }
.ant-menu-item.ant-menu-item-active:hover {
background: @layout-sidebar-selected-color;
} }
} }
// 侧边栏折叠时 // 侧边栏折叠时
@@ -668,7 +658,7 @@ body {
.ant-menu-submenu.ant-menu-submenu-placement-rightTop { .ant-menu-submenu.ant-menu-submenu-placement-rightTop {
> .ant-menu { > .ant-menu {
// background: url('../assets/sidebar_background.png'); // background: url('../assets/sidebar_background.png');
background: #1d264c; background: @layout-sidebar-color;
background-position-x: center; background-position-x: center;
background-position-y: center; background-position-y: center;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
@@ -830,14 +820,21 @@ body {
.el-input__inner { .el-input__inner {
border-radius: 2px !important; border-radius: 2px !important;
} }
.el-input__inner:hover {
border-color: #4596de !important; .el-input__inner:hover,
.el-select .el-input.is-focus .el-input__inner,
.el-select .el-input__inner:focus,
.el-button.is-plain:focus,
.el-button.is-plain:hover,
.el-input.is-active .el-input__inner,
.el-input__inner:focus {
border-color: #custom_colors() [color_1] !important;
} }
.el-select .el-input.is-focus .el-input__inner { .el-button--text,
border-color: #4596de !important; .el-select-dropdown__item.selected,
} .el-button.is-plain:focus,
.el-select-dropdown__item.selected { .el-button.is-plain:hover {
color: #4596de !important; color: #custom_colors() [color_1] !important;
} }
.ant-tabs-nav .ant-tabs-tab { .ant-tabs-nav .ant-tabs-tab {
@@ -897,7 +894,7 @@ body {
} }
} }
.custom-vue-treeselect__control(@bgColor:#f0f5ff,@border:none) { .custom-vue-treeselect__control(@bgColor:#custom_colors()[color_2],@border:none) {
background-color: @bgColor; background-color: @bgColor;
border: @border; border: @border;
} }
@@ -938,29 +935,29 @@ body {
.vue-treeselect__option--highlight, .vue-treeselect__option--highlight,
.vue-treeselect__option--selected { .vue-treeselect__option--selected {
color: #custom_colors[color_1]; color: #custom_colors[color_1];
background-color: #f0f5ff !important; background-color: #custom_colors() [color_2] !important;
} }
.vue-treeselect__checkbox--checked, .vue-treeselect__checkbox--checked,
.vue-treeselect__checkbox--indeterminate { .vue-treeselect__checkbox--indeterminate {
border-color: #2f54eb !important; border-color: #custom_colors() [color_1] !important;
background: #2f54eb !important; background: #custom_colors() [color_1] !important;
} }
.vue-treeselect__label-container:hover { .vue-treeselect__label-container:hover {
.vue-treeselect__checkbox--checked, .vue-treeselect__checkbox--checked,
.vue-treeselect__checkbox--indeterminate { .vue-treeselect__checkbox--indeterminate {
border-color: #2f54eb !important; border-color: #custom_colors() [color_1] !important;
background: #2f54eb !important; background: #custom_colors() [color_1] !important;
} }
} }
.vue-treeselect__multi-value-item { .vue-treeselect__multi-value-item {
background: #f0f5ff !important; background: #custom_colors() [color_2] !important;
color: #2f54eb !important; color: #custom_colors() [color_1] !important;
} }
.vue-treeselect__value-remove { .vue-treeselect__value-remove {
color: #2f54eb !important; color: #custom_colors() [color_1] !important;
} }
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--unchecked { .vue-treeselect__label-container:hover .vue-treeselect__checkbox--unchecked {
border-color: #2f54eb !important; border-color: #custom_colors() [color_1] !important;
} }
//表格样式 //表格样式
@@ -970,7 +967,7 @@ body {
border: none !important; border: none !important;
} }
.vxe-table--header-wrapper { .vxe-table--header-wrapper {
background-color: #f0f5ff !important; background-color: #custom_colors() [color_2] !important;
} }
.vxe-header--row .vxe-header--column:hover { .vxe-header--row .vxe-header--column:hover {
background: #2f54eb1f !important; background: #2f54eb1f !important;
@@ -994,7 +991,7 @@ body {
border: none !important; border: none !important;
} }
.vxe-table--header-wrapper { .vxe-table--header-wrapper {
background-color: #f0f5ff !important; background-color: #custom_colors() [color_2] !important;
} }
// .vxe-table--header-wrapper.body--wrapper { // .vxe-table--header-wrapper.body--wrapper {
// border-radius: 8px !important; // border-radius: 8px !important;
@@ -1088,8 +1085,34 @@ body {
.vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon, .vxe-table--render-default .vxe-cell--checkbox:not(.is--disabled):hover .vxe-checkbox--icon,
.is--filter-active .vxe-cell--filter .vxe-filter--btn, .is--filter-active .vxe-cell--filter .vxe-filter--btn,
.vxe-table .vxe-sort--asc-btn.sort--active, .vxe-table .vxe-sort--asc-btn.sort--active,
.vxe-table .vxe-sort--desc-btn.sort--active { .vxe-table .vxe-sort--desc-btn.sort--active,
color: #2f54eb !important; .vxe-select-option.is--selected,
.vxe-loading > .vxe-loading--chunk,
.vxe-loading > .vxe-loading--warpper,
.vxe-pager .vxe-pager--jump-next:not(.is--disabled).is--active,
.vxe-pager .vxe-pager--jump-next:not(.is--disabled):focus,
.vxe-pager .vxe-pager--jump-prev:not(.is--disabled).is--active,
.vxe-pager .vxe-pager--jump-prev:not(.is--disabled):focus,
.vxe-pager .vxe-pager--next-btn:not(.is--disabled).is--active,
.vxe-pager .vxe-pager--next-btn:not(.is--disabled):focus,
.vxe-pager .vxe-pager--num-btn:not(.is--disabled).is--active,
.vxe-pager .vxe-pager--num-btn:not(.is--disabled):focus,
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled).is--active,
.vxe-pager .vxe-pager--prev-btn:not(.is--disabled):focus,
.vxe-button.type--text:not(.is--disabled):hover,
.vxe-table--filter-footer > button:hover {
color: #custom_colors() [color_1] !important;
}
.vxe-cell .vxe-default-input:focus,
.vxe-cell .vxe-default-select:focus,
.vxe-cell .vxe-default-textarea:focus,
.vxe-table--filter-wrapper .vxe-default-input:focus,
.vxe-table--filter-wrapper .vxe-default-select:focus,
.vxe-table--filter-wrapper .vxe-default-textarea:focus,
.vxe-select.is--active:not(.is--filter) > .vxe-input .vxe-input--inner,
.vxe-input:not(.is--disabled).is--active .vxe-input--inner {
border-color: #custom_colors() [color_1] !important;
} }
//批量操作 //批量操作
@@ -1107,7 +1130,7 @@ body {
} }
} }
> span:last-child { > span:last-child {
color: rgba(47, 84, 235, 0.55); color: #custom_colors[color_1];
cursor: default; cursor: default;
} }
} }
@@ -1117,7 +1140,7 @@ body {
.ant-tabs-card-bar { .ant-tabs-card-bar {
margin: 0; margin: 0;
.ant-tabs-nav-container { .ant-tabs-nav-container {
background-color: #custom_colors[color_2]; background-color: #fff;
.ant-tabs-tab { .ant-tabs-tab {
border: none; border: none;
border-top-left-radius: 4px; border-top-left-radius: 4px;
@@ -1125,9 +1148,9 @@ body {
background: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.5);
margin-right: 5px; margin-right: 5px;
} }
} .ant-tabs-tab-active {
.ant-tabs-tab-active { background: #custom_colors[color_2];
background: #fff !important; }
} }
} }
} }
@@ -1218,7 +1241,7 @@ body {
.el-tabs__header { .el-tabs__header {
border-bottom: none; border-bottom: none;
background-color: #f0f5ff; background-color: #custom_colors[color_2];
border-radius: 8px 8px 0px 0px; border-radius: 8px 8px 0px 0px;
} }
@@ -1303,6 +1326,15 @@ body {
} }
} }
// json editor
.jsoneditor-vue {
div.jsoneditor {
border: none;
}
div.jsoneditor-menu {
border-bottom-color: #custom_colors[color_1];
}
}
// .ant-menu.ant-menu-light { // .ant-menu.ant-menu-light {
// &.ops-menu { // &.ops-menu {
// background-color: white; // background-color: white;

View File

@@ -1,7 +1,7 @@
@border-radius-base: 2px; // 组件/浮层圆角 @border-radius-base: 2px; // 组件/浮层圆角
@primary-color: #2f54eb; // 全局主色 @primary-color: #2f54eb; // 全局主色
@scrollbar-color: rgba(47, 122, 235, 0.2);
// @layout-header-background: #1a3652;
@layout-header-background: #fff; @layout-header-background: #fff;
@layout-header-height: 40px; @layout-header-height: 40px;
@layout-header-icon-height: 34px; @layout-header-icon-height: 34px;
@@ -15,13 +15,20 @@
@layout-background-light-color: #fafafa; @layout-background-light-color: #fafafa;
@layout-background-light-color-light: #f0f0f0; @layout-background-light-color-light: #f0f0f0;
@layout-sidebar-color: #1d264c; //bg
@layout-sidebar-sub-color: #000c37; //bg
@layout-sidebar-selected-color: #3e4a71; //selected bg
@layout-sidebar-arrow-color: rgba(255, 255, 255, 0.6);
@layout-sidebar-font-color: #fff;
@layout-sidebar-disabled-font-color: rgba(255, 255, 255, 0.6);
#custom_colors() { #custom_colors() {
color_1: #2f54eb; color_1: #2f54eb; //primary color
color_2: #f0f5ff; color_2: #f0f5ff; //light background color
color_3: #D2E2FF; color_3: #d2e2ff;
} }
.ops_display_wrapper(@backgroundColor:#f0f5ff) { .ops_display_wrapper(@backgroundColor:#custom_colors()[color_2]) {
cursor: pointer; cursor: pointer;
padding: 5px 8px; padding: 5px 8px;
background-color: @backgroundColor; background-color: @backgroundColor;

View File

@@ -76,7 +76,8 @@ const AppDeviceEnquire = {
const mixinPermissions = { const mixinPermissions = {
computed: { computed: {
...mapState({ ...mapState({
detailPermissions: state => state.user.detailPermissions detailPermissions: state => state.user.detailPermissions,
roles: state => state.user.roles
}) })
}, },
methods: { methods: {
@@ -85,7 +86,7 @@ const mixinPermissions = {
hasDetailPermission(appName, resourceName, perms = []) { hasDetailPermission(appName, resourceName, perms = []) {
const appNamePer = this.detailPermissions[`${appName}`] const appNamePer = this.detailPermissions[`${appName}`]
const _findResourcePermissions = appNamePer.find(item => item.name === resourceName) const _findResourcePermissions = appNamePer.find(item => item.name === resourceName)
return _findResourcePermissions.permissions.some(item => perms.includes(item)) return this.roles?.permissions.includes('acl_admin') || this.roles?.permissions.includes('backend_admin') || _findResourcePermissions?.permissions.some(item => perms.includes(item))
} }
} }
} }

View File

@@ -60,34 +60,6 @@
<vxe-column field="mobile" :title="$t('cs.companyStructure.mobile')" min-width="80"></vxe-column> <vxe-column field="mobile" :title="$t('cs.companyStructure.mobile')" min-width="80"></vxe-column>
<vxe-column field="position_name" :title="$t('cs.companyStructure.positionName')" min-width="80"></vxe-column> <vxe-column field="position_name" :title="$t('cs.companyStructure.positionName')" min-width="80"></vxe-column>
<vxe-column field="department_name" :title="$t('cs.companyStructure.departmentName')" min-width="80"></vxe-column> <vxe-column field="department_name" :title="$t('cs.companyStructure.departmentName')" min-width="80"></vxe-column>
<vxe-column field="current_company" v-if="useDFC" :title="$t('cs.companyStructure.currentCompany')" min-width="120"></vxe-column>
<vxe-column field="dfc_entry_date" v-if="useDFC" :title="$t('cs.companyStructure.dfcEntryDate')" min-width="120"></vxe-column>
<vxe-column field="entry_date" :title="$t('cs.companyStructure.entryDate')" min-width="120"></vxe-column>
<vxe-column field="is_internship" :title="$t('cs.companyStructure.isInternship')" min-width="120"></vxe-column>
<vxe-column field="leave_date" :title="$t('cs.companyStructure.leaveDate')" min-width="120"></vxe-column>
<vxe-column field="id_card" :title="$t('cs.companyStructure.idCard')" min-width="120"></vxe-column>
<vxe-column field="nation" :title="$t('cs.companyStructure.nation')" min-width="80"></vxe-column>
<vxe-column field="id_place" :title="$t('cs.companyStructure.idPlace')" min-width="80"></vxe-column>
<vxe-column field="party" :title="$t('cs.companyStructure.party')" min-width="80"></vxe-column>
<vxe-column field="household_registration_type" :title="$t('cs.companyStructure.householdRegistrationType')" min-width="80"></vxe-column>
<vxe-column field="hometown" :title="$t('cs.companyStructure.homewtown')" min-width="80"></vxe-column>
<vxe-column field="marry" :title="$t('cs.companyStructure.marry')" min-width="80"></vxe-column>
<vxe-column field="max_degree" :title="$t('cs.companyStructure.maxDegree')" min-width="80"></vxe-column>
<vxe-column field="emergency_person" :title="$t('cs.companyStructure.emergencyPerson')" min-width="120"></vxe-column>
<vxe-column field="emergency_phone" :title="$t('cs.companyStructure.emergencyPhone')" min-width="120"></vxe-column>
<vxe-column field="bank_card_number" :title="$t('cs.companyStructure.bankCardNumber')" min-width="120"></vxe-column>
<vxe-column field="bank_card_name" :title="$t('cs.companyStructure.bankCardName')" min-width="80"></vxe-column>
<vxe-column field="opening_bank" :title="$t('cs.companyStructure.openingBank')" min-width="80"></vxe-column>
<vxe-column field="account_opening_location" :title="$t('cs.companyStructure.accountOpeningLocation')" min-width="120"></vxe-column>
<vxe-column field="school" :title="$t('cs.companyStructure.school')" min-width="80"></vxe-column>
<vxe-column field="major" :title="$t('cs.companyStructure.major')" min-width="80"></vxe-column>
<vxe-column field="education" :title="$t('cs.companyStructure.education')" min-width="80"></vxe-column>
<vxe-column field="graduation_year" :title="$t('cs.companyStructure.graduationYear')" min-width="120"></vxe-column>
<vxe-column field="birth_date" :title="$t('cs.companyStructure.birthDate')" min-width="120"></vxe-column>
<vxe-column field="birth_place" :title="$t('cs.companyStructure.birthPlace')" min-width="120"></vxe-column>
<vxe-column field="nationality_region" :title="$t('cs.companyStructure.nationalityRegion')" min-width="120"></vxe-column>
<vxe-column field="first_entry_date" :title="$t('cs.companyStructure.firstEntryDate')" min-width="120"></vxe-column>
<vxe-column field="estimated_departure_date" :title="$t('cs.companyStructure.estimatedDepartureDate')" min-width="120"></vxe-column>
<vxe-column v-if="has_error" field="err" :title="$t('cs.companyStructure.importFailedReason')" min-width="120" fixed="right"> <vxe-column v-if="has_error" field="err" :title="$t('cs.companyStructure.importFailedReason')" min-width="120" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<span :style="{ color: '#D81E06' }">{{ row.err }}</span> <span :style="{ color: '#D81E06' }">{{ row.err }}</span>
@@ -303,98 +275,6 @@ export default {
v: '部门', v: '部门',
t: 's', t: 's',
}, },
{
v: '目前所属主体',
t: 's',
},
{
v: '初始入职日期',
t: 's',
},
{
v: '目前主体入职日期',
t: 's',
},
{
v: '正式/实习生',
t: 's',
},
{
v: '离职日期',
t: 's',
},
{
v: '身份证号码',
t: 's',
},
{
v: '民族',
t: 's',
},
{
v: '籍贯',
t: 's',
},
{
v: '组织关系',
t: 's',
},
{
v: '户籍类型',
t: 's',
},
{
v: '户口所在地',
t: 's',
},
{
v: '婚姻情况',
t: 's',
},
{
v: '最高学历',
t: 's',
},
{
v: '紧急联系人',
t: 's',
},
{
v: '紧急联系电话',
t: 's',
},
{
v: '卡号',
t: 's',
},
{
v: '银行',
t: 's',
},
{
v: '开户行',
t: 's',
},
{
v: '开户地',
t: 's',
},
{
v: '学校',
t: 's',
},
{
v: '专业',
t: 's',
},
{
v: '学历',
t: 's',
},
{
v: '毕业年份',
t: 's',
},
], ],
] ]
data[1] = data[1].filter((item) => item['v'] !== '目前所属主体') data[1] = data[1].filter((item) => item['v'] !== '目前所属主体')

View File

@@ -372,7 +372,7 @@ export default {
{ label: this.$t('cs.companyStructure.mobile'), value: 'mobile' }, { label: this.$t('cs.companyStructure.mobile'), value: 'mobile' },
{ label: this.$t('cs.companyStructure.departmentName'), value: 'department_name' }, { label: this.$t('cs.companyStructure.departmentName'), value: 'department_name' },
{ label: this.$t('cs.companyStructure.positionName'), value: 'position_name' }, { label: this.$t('cs.companyStructure.positionName'), value: 'position_name' },
{ label: this.$t('cs.companyStructure.departmentDirector'), value: 'direct_supervisor_id' }, { label: this.$t('cs.companyStructure.supervisor'), value: 'direct_supervisor_id' },
] ]
}, },
sceneList () { sceneList () {

View File

@@ -18,7 +18,7 @@ module.exports = {
// TODO 需要增加根据环境不开启主题需求 // TODO 需要增加根据环境不开启主题需求
new ThemeColorReplacer({ new ThemeColorReplacer({
fileName: 'css/theme-colors-[contenthash:8].css', fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials('#1890ff'), // 主色系列 matchColors: getAntdSerials('#2f54eb'), // 主色系列
// 改变样式选择器,解决样式覆盖问题 // 改变样式选择器,解决样式覆盖问题
changeSelector(selector) { changeSelector(selector) {
switch (selector) { switch (selector) {
@@ -83,11 +83,9 @@ module.exports = {
less: { less: {
modifyVars: { modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */ /* less 变量覆盖,用于自定义 ant design 主题 */
/* 'primary-color': '#2f54eb',
'primary-color': '#F5222D', // 'link-color': '#F5222D',
'link-color': '#F5222D', // 'border-radius-base': '4px',
'border-radius-base': '4px',
*/
}, },
javascriptEnabled: true, javascriptEnabled: true,
}, },