mirror of
https://github.com/veops/cmdb.git
synced 2025-09-10 06:30:52 +08:00
Compare commits
54 Commits
deploy_073
...
v2.4.12
Author | SHA1 | Date | |
---|---|---|---|
|
251b9e7fd5 | ||
|
f3cc12f1f9 | ||
|
56f03e1624 | ||
|
42ad2b6dde | ||
|
5aba1ff257 | ||
|
417e8fe349 | ||
|
02235d8cc0 | ||
|
00c7a644a2 | ||
|
f3e8757450 | ||
|
f0749341ba | ||
|
89da671e46 | ||
|
0e60aae076 | ||
|
4dfa97d404 | ||
|
9b778f9bc7 | ||
|
eafb5f053a | ||
|
834054e216 | ||
|
a97cabbedc | ||
|
ae77852d5f | ||
|
611ee40dca | ||
|
c0d55b2126 | ||
|
2cc4499ef9 | ||
|
1268404bca | ||
|
570a9203c4 | ||
|
adae7b5519 | ||
|
8a91ec7b11 | ||
|
92fca65383 | ||
|
4b8e6c2841 | ||
|
ab240cb003 | ||
|
61e62e4740 | ||
|
1fd72d6c78 | ||
|
51e16f6b23 | ||
|
037378e384 | ||
|
631871a8cf | ||
|
6e02f6a21f | ||
|
a2224ba2ac | ||
|
11a289aac9 | ||
|
55ab04dd28 | ||
|
256a4f4844 | ||
|
018a349336 | ||
|
8f62227adb | ||
|
de51cb3e21 | ||
|
ecb069cf14 | ||
|
937cb84393 | ||
|
40a4db06b5 | ||
|
cc98f903ea | ||
|
fb7471ce04 | ||
|
e2872f041e | ||
|
250fde127c | ||
|
73dbb14944 | ||
|
73c9a6fa72 | ||
|
09d957db79 | ||
|
b73d796891 | ||
|
e7cbd0caa9 | ||
|
3e4c385d91 |
62
.github/workflows/docker-build-and-release.yaml
vendored
62
.github/workflows/docker-build-and-release.yaml
vendored
@@ -5,9 +5,9 @@ on:
|
||||
branches:
|
||||
- master
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - master
|
||||
|
||||
env:
|
||||
# Use docker.io for Docker Hub if empty
|
||||
@@ -49,31 +49,31 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY_SERVER_ADDRESS }}/cmdb-api:${{ env.TAG }}
|
||||
release-ui-images:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup-environment]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Login to GitHub Package Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push CMDB-UI Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: docker/Dockerfile-UI
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY_SERVER_ADDRESS }}/cmdb-ui:${{ env.TAG }}
|
||||
# release-ui-images:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: [setup-environment]
|
||||
# permissions:
|
||||
# contents: read
|
||||
# packages: write
|
||||
# timeout-minutes: 90
|
||||
# steps:
|
||||
# - name: Checkout Repo
|
||||
# uses: actions/checkout@v4
|
||||
# - name: Login to GitHub Package Registry
|
||||
# uses: docker/login-action@v2
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.repository_owner }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
# - name: Set up QEMU
|
||||
# uses: docker/setup-qemu-action@v3
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v3
|
||||
# - name: Build and push CMDB-UI Docker image
|
||||
# uses: docker/build-push-action@v6
|
||||
# with:
|
||||
# file: docker/Dockerfile-UI
|
||||
# context: .
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
# push: true
|
||||
# tags: ${{ env.REGISTRY_SERVER_ADDRESS }}/cmdb-ui:${{ env.TAG }}
|
@@ -108,7 +108,8 @@ class AttributeManager(object):
|
||||
return []
|
||||
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
|
||||
|
||||
return [[ValueTypeMap.serialize[value_type](choice_value['value']), choice_value['option']]
|
||||
return [[ValueTypeMap.serialize[value_type](choice_value['value']), choice_value['option'] or
|
||||
{"label": ValueTypeMap.serialize[value_type](choice_value['value'])}]
|
||||
for choice_value in choice_values]
|
||||
|
||||
@staticmethod
|
||||
@@ -135,6 +136,15 @@ class AttributeManager(object):
|
||||
choice_table and choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||
db.session.flush()
|
||||
|
||||
@classmethod
|
||||
def get_enum_map(cls, _attr_id, _attr=None):
|
||||
attr = AttributeCache.get(_attr_id) if _attr_id else _attr
|
||||
if attr and attr.is_choice:
|
||||
choice_values = cls.get_choice_values(attr.id, attr.value_type, None, None)
|
||||
return {i[0]: i[1]['label'] for i in choice_values if i[1] and i[1].get('label')}
|
||||
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def search_attributes(cls, name=None, alias=None, page=1, page_size=None):
|
||||
"""
|
||||
@@ -167,24 +177,30 @@ class AttributeManager(object):
|
||||
def get_attribute_by_name(self, name):
|
||||
attr = Attribute.get_by(name=name, first=True)
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
|
||||
attr["choice_web_hook"], attr.get("choice_other"))
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"],
|
||||
attr["value_type"],
|
||||
attr["choice_web_hook"],
|
||||
attr.get("choice_other"))
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute_by_alias(self, alias):
|
||||
attr = Attribute.get_by(alias=alias, first=True)
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
|
||||
attr["choice_web_hook"], attr.get("choice_other"))
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"],
|
||||
attr["value_type"],
|
||||
attr["choice_web_hook"],
|
||||
attr.get("choice_other"))
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute_by_id(self, _id):
|
||||
attr = Attribute.get_by_id(_id).to_dict()
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
|
||||
attr["choice_web_hook"], attr.get("choice_other"))
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"],
|
||||
attr["value_type"],
|
||||
attr["choice_web_hook"],
|
||||
attr.get("choice_other"))
|
||||
|
||||
return attr
|
||||
|
||||
|
@@ -2,12 +2,13 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from flask import current_app
|
||||
|
||||
import json
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
@@ -254,7 +255,7 @@ class CMDBCounterCache(object):
|
||||
|
||||
@classmethod
|
||||
def set(cls, result):
|
||||
cache.set(cls.KEY, result, timeout=0)
|
||||
cache.set(cls.KEY, json.loads(json.dumps(result)), timeout=0)
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
@@ -276,7 +277,7 @@ class CMDBCounterCache(object):
|
||||
|
||||
cls.set(result)
|
||||
|
||||
return result
|
||||
return json.loads(json.dumps(result))
|
||||
|
||||
@classmethod
|
||||
def update(cls, custom, flush=True):
|
||||
@@ -298,25 +299,36 @@ class CMDBCounterCache(object):
|
||||
result[custom['id']] = res
|
||||
cls.set(result)
|
||||
|
||||
return res
|
||||
return json.loads(json.dumps(res))
|
||||
|
||||
@staticmethod
|
||||
def relation_counter(type_id, level, other_filer, type_ids):
|
||||
@classmethod
|
||||
def relation_counter(cls, type_id, level, other_filer, type_ids):
|
||||
from api.lib.cmdb.search.ci_relation.search import Search as RelSearch
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
|
||||
query = "_type:{}".format(type_id)
|
||||
if other_filer:
|
||||
query = "{},{}".format(query, other_filer)
|
||||
s = search(query, count=1000000)
|
||||
try:
|
||||
type_names, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
root_type = CITypeCache.get(type_id)
|
||||
show_attr_id = root_type and root_type.show_id
|
||||
show_attr = AttributeCache.get(show_attr_id)
|
||||
|
||||
type_id_names = [(str(i.get('_id')), i.get(i.get('unique'))) for i in type_names]
|
||||
type_id_names = []
|
||||
for i in type_names:
|
||||
attr_value = i.get(show_attr and show_attr.name) or i.get(i.get('unique'))
|
||||
enum_map = AttributeManager.get_enum_map(show_attr_id or i.get('unique'))
|
||||
|
||||
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
||||
type_id_names.append((str(i.get('_id')), enum_map.get(attr_value, attr_value)))
|
||||
|
||||
s = RelSearch([i[0] for i in type_id_names], level)
|
||||
try:
|
||||
stats = s.statistics(type_ids, need_filter=False)
|
||||
except SearchError as e:
|
||||
@@ -346,11 +358,12 @@ class CMDBCounterCache(object):
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def attribute_counter(custom):
|
||||
@classmethod
|
||||
def attribute_counter(cls, custom):
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
|
||||
custom.setdefault('options', {})
|
||||
type_id = custom.get('type_id')
|
||||
@@ -366,16 +379,24 @@ class CMDBCounterCache(object):
|
||||
other_filter = "{}".format(other_filter) if other_filter else ''
|
||||
|
||||
if custom['options'].get('ret') == 'cis':
|
||||
enum_map = {}
|
||||
for _attr_id in attr_ids:
|
||||
_attr = AttributeCache.get(_attr_id)
|
||||
if _attr:
|
||||
enum_map[_attr.alias] = AttributeManager.get_enum_map(_attr_id)
|
||||
|
||||
query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
|
||||
s = search(query, fl=attr_ids, ret_key='alias', count=100)
|
||||
try:
|
||||
cis, _, _, _, _, _ = s.search()
|
||||
cis = [{k: (enum_map.get(k) or {}).get(v, v) for k, v in ci.items()} for ci in cis]
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
|
||||
return cis
|
||||
|
||||
origin_result = dict()
|
||||
result = dict()
|
||||
# level = 1
|
||||
query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
|
||||
@@ -385,13 +406,18 @@ class CMDBCounterCache(object):
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
|
||||
enum_map1 = AttributeManager.get_enum_map(attr_ids[0])
|
||||
for i in (list(facet.values()) or [[]])[0]:
|
||||
result[ValueTypeMap.serialize2[attr2value_type[0]](str(i[0]))] = i[1]
|
||||
k = ValueTypeMap.serialize2[attr2value_type[0]](str(i[0]))
|
||||
result[enum_map1.get(k, k)] = i[1]
|
||||
origin_result[k] = i[1]
|
||||
if len(attr_ids) == 1:
|
||||
return result
|
||||
|
||||
# level = 2
|
||||
for v in result:
|
||||
enum_map2 = AttributeManager.get_enum_map(attr_ids[1])
|
||||
for v in origin_result:
|
||||
query = "_type:({}),{},{}:{}".format(";".join(map(str, type_ids)), other_filter, attr_ids[0], v)
|
||||
s = search(query, fl=attr_ids, facet=[attr_ids[1]], count=1)
|
||||
try:
|
||||
@@ -399,18 +425,22 @@ class CMDBCounterCache(object):
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
result[v] = dict()
|
||||
result[enum_map1.get(v, v)] = dict()
|
||||
origin_result[v] = dict()
|
||||
for i in (list(facet.values()) or [[]])[0]:
|
||||
result[v][ValueTypeMap.serialize2[attr2value_type[1]](str(i[0]))] = i[1]
|
||||
k = ValueTypeMap.serialize2[attr2value_type[1]](str(i[0]))
|
||||
result[enum_map1.get(v, v)][enum_map2.get(k, k)] = i[1]
|
||||
origin_result[v][k] = i[1]
|
||||
|
||||
if len(attr_ids) == 2:
|
||||
return result
|
||||
|
||||
# level = 3
|
||||
for v1 in result:
|
||||
if not isinstance(result[v1], dict):
|
||||
enum_map3 = AttributeManager.get_enum_map(attr_ids[2])
|
||||
for v1 in origin_result:
|
||||
if not isinstance(result[enum_map1.get(v1, v1)], dict):
|
||||
continue
|
||||
for v2 in result[v1]:
|
||||
for v2 in origin_result[v1]:
|
||||
query = "_type:({}),{},{}:{},{}:{}".format(";".join(map(str, type_ids)), other_filter,
|
||||
attr_ids[0], v1, attr_ids[1], v2)
|
||||
s = search(query, fl=attr_ids, facet=[attr_ids[2]], count=1)
|
||||
@@ -419,9 +449,10 @@ class CMDBCounterCache(object):
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
result[v1][v2] = dict()
|
||||
result[enum_map1.get(v1, v1)][enum_map2.get(v2, v2)] = dict()
|
||||
for i in (list(facet.values()) or [[]])[0]:
|
||||
result[v1][v2][ValueTypeMap.serialize2[attr2value_type[2]](str(i[0]))] = i[1]
|
||||
k = ValueTypeMap.serialize2[attr2value_type[2]](str(i[0]))
|
||||
result[enum_map1.get(v1, v1)][enum_map2.get(v2, v2)][enum_map3.get(k, k)] = i[1]
|
||||
|
||||
return result
|
||||
|
||||
@@ -525,19 +556,18 @@ class CMDBCounterCache(object):
|
||||
|
||||
@classmethod
|
||||
def flush_sub_counter(cls):
|
||||
result = dict(type_id2users=dict())
|
||||
result = dict(type_id2users=defaultdict(list))
|
||||
|
||||
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)
|
||||
result['type_id2users'][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)
|
||||
|
||||
@@ -557,7 +587,8 @@ class AutoDiscoveryMappingCache(object):
|
||||
def get(cls, name):
|
||||
res = cache.get(cls.PREFIX.format(name)) or {}
|
||||
if not res:
|
||||
path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "auto_discovery/mapping/{}.yaml".format(name))
|
||||
path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
|
||||
"auto_discovery/mapping/{}.yaml".format(name))
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as f:
|
||||
mapping = yaml.safe_load(f)
|
||||
|
@@ -161,7 +161,7 @@ class CIManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ci_by_id_from_db(cls, ci_id, ret_key=RetKey.NAME, fields=None, need_children=True, use_master=False,
|
||||
valid=False):
|
||||
valid=False, enum_use_label=False):
|
||||
"""
|
||||
|
||||
:param ci_id:
|
||||
@@ -170,6 +170,7 @@ class CIManager(object):
|
||||
:param need_children:
|
||||
:param use_master: whether to use master db
|
||||
:param valid:
|
||||
:param enum_use_label:
|
||||
:return:
|
||||
"""
|
||||
|
||||
@@ -187,13 +188,19 @@ class CIManager(object):
|
||||
|
||||
res["ci_type"] = ci_type.name
|
||||
|
||||
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
|
||||
enum_map = dict()
|
||||
if not enum_use_label:
|
||||
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
|
||||
else:
|
||||
fields, enum_map = CITypeAttributeManager.get_attr_names_label_enum(
|
||||
ci.type_id) if not fields else (fields, {})
|
||||
unique_key = AttributeCache.get(ci_type.unique_id)
|
||||
_res = AttributeValueManager().get_attr_values(fields,
|
||||
ci_id,
|
||||
ret_key=ret_key,
|
||||
unique_key=unique_key,
|
||||
use_master=use_master)
|
||||
use_master=use_master,
|
||||
enum_map=enum_map)
|
||||
res.update(_res)
|
||||
|
||||
res['_type'] = ci_type.id
|
||||
@@ -266,7 +273,7 @@ class CIManager(object):
|
||||
value_table = TableMap(attr_name=id2name[attr_id]).table
|
||||
|
||||
values = value_table.get_by(attr_id=attr_id,
|
||||
value=ci_dict.get(id2name[attr_id]) or None,
|
||||
value=ci_dict.get(id2name[attr_id]),
|
||||
only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == type_id)
|
||||
_ci_ids = set([i.ci_id for i in values])
|
||||
@@ -292,6 +299,53 @@ class CIManager(object):
|
||||
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def _reference_to_ci_id(attr, payload):
|
||||
def __unique_value2id(_type, _v):
|
||||
value_table = TableMap(attr_name=_type.unique_id).table
|
||||
ci = value_table.get_by(attr_id=attr.id, value=_v)
|
||||
if ci is not None:
|
||||
return ci.ci_id
|
||||
|
||||
return abort(400, ErrFormat.ci_reference_invalid.format(attr.alias, _v))
|
||||
|
||||
def __valid_reference_id_existed(_id, _type_id):
|
||||
ci = CI.get_by_id(_id) or abort(404, ErrFormat.ci_reference_not_found.format(attr.alias, _id))
|
||||
|
||||
if ci.type_id != _type_id:
|
||||
return abort(400, ErrFormat.ci_reference_invalid.format(attr.alias, _id))
|
||||
|
||||
if attr.name in payload:
|
||||
k, reference_value = attr.name, payload[attr.name]
|
||||
elif attr.alias in payload:
|
||||
k, reference_value = attr.alias, payload[attr.alias]
|
||||
else:
|
||||
return
|
||||
if not reference_value:
|
||||
return
|
||||
|
||||
reference_type = None
|
||||
if isinstance(reference_value, list):
|
||||
for idx, v in enumerate(reference_value):
|
||||
if isinstance(v, dict) and v.get('unique'):
|
||||
if reference_type is None:
|
||||
reference_type = CITypeCache.get(attr.reference_type_id)
|
||||
if reference_type is not None:
|
||||
reference_value[idx] = __unique_value2id(reference_type, v)
|
||||
else:
|
||||
__valid_reference_id_existed(v, attr.reference_type_id)
|
||||
|
||||
elif isinstance(reference_value, dict) and reference_value.get('unique'):
|
||||
if reference_type is None:
|
||||
reference_type = CITypeCache.get(attr.reference_type_id)
|
||||
if reference_type is not None:
|
||||
reference_value = __unique_value2id(reference_type, reference_value)
|
||||
elif str(reference_value).isdigit():
|
||||
reference_value = int(reference_value)
|
||||
__valid_reference_id_existed(reference_value, attr.reference_type_id)
|
||||
|
||||
payload[k] = reference_value
|
||||
|
||||
@classmethod
|
||||
def add(cls, ci_type_name,
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
@@ -392,6 +446,8 @@ class CIManager(object):
|
||||
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
|
||||
elif attr.is_reference:
|
||||
cls._reference_to_ci_id(attr, ci_dict)
|
||||
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
@@ -443,9 +499,10 @@ class CIManager(object):
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, _sync=False, **ci_dict):
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
ci_type = ci.ci_type
|
||||
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
@@ -475,11 +532,13 @@ class CIManager(object):
|
||||
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
|
||||
elif attr.is_reference:
|
||||
self._reference_to_ci_id(attr, ci_dict)
|
||||
|
||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||
|
||||
record_id = None
|
||||
with redis_lock.Lock(rd.r, ci.ci_type.name):
|
||||
with redis_lock.Lock(rd.r, ci_type.name):
|
||||
db.session.commit()
|
||||
|
||||
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
||||
@@ -510,14 +569,14 @@ class CIManager(object):
|
||||
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
||||
|
||||
if record_id or has_dynamic: # has changed
|
||||
if not __sync:
|
||||
if not _sync:
|
||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_cache(ci_id, OperateType.UPDATE, record_id)
|
||||
|
||||
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
|
||||
if ref_ci_dict:
|
||||
if not __sync:
|
||||
if not _sync:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_relation_add(ref_ci_dict, ci.id)
|
||||
@@ -578,7 +637,7 @@ class CIManager(object):
|
||||
if ci_dict:
|
||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||
|
||||
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||
ci_delete.apply_async(args=(ci_id, ci.type_id), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
return ci_id
|
||||
@@ -773,7 +832,7 @@ class CIManager(object):
|
||||
value_table = ValueTypeMap.table[ValueTypeEnum.PASSWORD]
|
||||
if current_app.config.get('SECRETS_ENGINE') == 'inner':
|
||||
if value:
|
||||
encrypt_value, status = InnerCrypt().encrypt(value)
|
||||
encrypt_value, status = InnerCrypt().encrypt(str(value))
|
||||
if not status:
|
||||
current_app.logger.error('save password failed: {}'.format(encrypt_value))
|
||||
return abort(400, ErrFormat.password_save_failed.format(encrypt_value))
|
||||
@@ -801,7 +860,7 @@ class CIManager(object):
|
||||
vault = VaultClient(current_app.config.get('VAULT_URL'), current_app.config.get('VAULT_TOKEN'))
|
||||
if value:
|
||||
try:
|
||||
vault.update("/{}/{}".format(ci_id, attr_id), dict(v=value))
|
||||
vault.update("/{}/{}".format(ci_id, attr_id), dict(v=str(value)))
|
||||
except Exception as e:
|
||||
current_app.logger.error('save password to vault failed: {}'.format(e))
|
||||
return abort(400, ErrFormat.password_save_failed.format('write vault failed'))
|
||||
@@ -1457,7 +1516,8 @@ class CITriggerManager(object):
|
||||
cls._update_old_attr_value(record_id, ci_dict)
|
||||
|
||||
if ci_id is not None:
|
||||
ci_dict = CIManager().get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
ci_dict = CIManager().get_ci_by_id_from_db(
|
||||
ci_id, need_children=False, use_master=False, enum_use_label=True)
|
||||
|
||||
try:
|
||||
response = webhook_request(webhook, ci_dict).text
|
||||
@@ -1484,7 +1544,8 @@ class CITriggerManager(object):
|
||||
with app.app_context():
|
||||
|
||||
if ci_id is not None:
|
||||
ci_dict = CIManager().get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
ci_dict = CIManager().get_ci_by_id_from_db(
|
||||
ci_id, need_children=False, use_master=False, enum_use_label=True)
|
||||
|
||||
if operate_type == OperateType.UPDATE:
|
||||
cls._update_old_attr_value(record_id, ci_dict)
|
||||
|
@@ -1,5 +1,7 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import copy
|
||||
import toposort
|
||||
from flask import abort
|
||||
@@ -145,7 +147,7 @@ class CITypeManager(object):
|
||||
kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"]
|
||||
|
||||
cls._validate_unique(name=kwargs['name'])
|
||||
cls._validate_unique(alias=kwargs['alias'])
|
||||
# cls._validate_unique(alias=kwargs['alias'])
|
||||
|
||||
kwargs["unique_id"] = unique_key.id
|
||||
kwargs['uid'] = current_user.uid
|
||||
@@ -184,7 +186,7 @@ class CITypeManager(object):
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
|
||||
cls._validate_unique(type_id=type_id, name=kwargs.get('name'))
|
||||
cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name'))
|
||||
# cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name'))
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None)
|
||||
unique_key = AttributeCache.get(unique_key)
|
||||
@@ -234,6 +236,10 @@ class CITypeManager(object):
|
||||
if CITypeInheritance.get_by(parent_id=type_id, first=True):
|
||||
return abort(400, ErrFormat.ci_type_inheritance_cannot_delete)
|
||||
|
||||
reference = Attribute.get_by(reference_type_id=type_id, first=True, to_dict=False)
|
||||
if reference is not None:
|
||||
return abort(400, ErrFormat.ci_type_referenced_cannot_delete.format(reference.alias))
|
||||
|
||||
relation_views = PreferenceRelationView.get_by(to_dict=False)
|
||||
for rv in relation_views:
|
||||
for item in (rv.cr_ids or []):
|
||||
@@ -343,9 +349,9 @@ class CITypeInheritanceManager(object):
|
||||
@classmethod
|
||||
def add(cls, parent_ids, child_id):
|
||||
|
||||
rels = {}
|
||||
rels = defaultdict(set)
|
||||
for i in cls.cls.get_by(to_dict=False):
|
||||
rels.setdefault(i.child_id, set()).add(i.parent_id)
|
||||
rels[i.child_id].add(i.parent_id)
|
||||
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
@@ -359,7 +365,7 @@ class CITypeInheritanceManager(object):
|
||||
|
||||
existed = cls.cls.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
|
||||
if existed is None:
|
||||
rels.setdefault(child_id, set()).add(parent_id)
|
||||
rels[child_id].add(parent_id)
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
except toposort.CircularDependencyError as e:
|
||||
@@ -500,14 +506,13 @@ class CITypeAttributeManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_attr_name(ci_type_name, key):
|
||||
@classmethod
|
||||
def get_attr_name(cls, ci_type_name, key):
|
||||
ci_type = CITypeCache.get(ci_type_name)
|
||||
if ci_type is None:
|
||||
return
|
||||
|
||||
for i in CITypeAttributesCache.get(ci_type.id):
|
||||
attr = AttributeCache.get(i.attr_id)
|
||||
for _, attr in cls.get_all_attributes(ci_type.id):
|
||||
if attr and (attr.name == key or attr.alias == key):
|
||||
return attr.name
|
||||
|
||||
@@ -519,12 +524,31 @@ class CITypeAttributeManager(object):
|
||||
for _type_id in parent_ids + [type_id]:
|
||||
result.extend(CITypeAttributesCache.get2(_type_id))
|
||||
|
||||
return result
|
||||
attr_ids = set()
|
||||
result2 = []
|
||||
for i in result:
|
||||
if i[1].id not in attr_ids:
|
||||
result2.append(i)
|
||||
attr_ids.add(i[1].id)
|
||||
|
||||
return result2
|
||||
|
||||
@classmethod
|
||||
def get_attr_names_by_type_id(cls, type_id):
|
||||
return [attr.name for _, attr in cls.get_all_attributes(type_id)]
|
||||
|
||||
@classmethod
|
||||
def get_attr_names_label_enum(cls, type_id):
|
||||
attr_names, enum_map = list(), defaultdict(dict)
|
||||
for _, attr in cls.get_all_attributes(type_id):
|
||||
attr_names.append(attr.name)
|
||||
if attr.is_choice and not attr.choice_other and not attr.choice_web_hook:
|
||||
_map = AttributeManager.get_enum_map(attr.id)
|
||||
if _map:
|
||||
enum_map[attr.name].update(_map)
|
||||
|
||||
return attr_names, enum_map
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
has_config_perm = ACLManager('cmdb').has_permission(
|
||||
@@ -565,10 +589,10 @@ class CITypeAttributeManager(object):
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
result = {type_id: [i for _, i in cls.get_all_attributes(type_id)] for type_id in type_ids}
|
||||
attr2types = {}
|
||||
attr2types = defaultdict(list)
|
||||
for type_id in result:
|
||||
for i in result[type_id]:
|
||||
attr2types.setdefault(i.id, []).append(type_id)
|
||||
attr2types[i.id].append(type_id)
|
||||
|
||||
attrs = []
|
||||
for attr_id in attr2types:
|
||||
@@ -846,12 +870,12 @@ class CITypeRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def recursive_level2children(cls, parent_id):
|
||||
result = dict()
|
||||
result = defaultdict(list)
|
||||
|
||||
def get_children(_id, level):
|
||||
children = CITypeRelation.get_by(parent_id=_id, to_dict=False)
|
||||
if children:
|
||||
result.setdefault(level + 1, []).extend([i.child.to_dict() for i in children])
|
||||
result[level + 1].extend([i.child.to_dict() for i in children])
|
||||
|
||||
for i in children:
|
||||
if i.child_id != _id:
|
||||
@@ -950,10 +974,10 @@ class CITypeRelationManager(object):
|
||||
p = CITypeManager.check_is_existed(parent)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
rels = {}
|
||||
rels = defaultdict(set)
|
||||
for i in CITypeRelation.get_by(to_dict=False):
|
||||
rels.setdefault(i.child_id, set()).add(i.parent_id)
|
||||
rels.setdefault(c.id, set()).add(p.id)
|
||||
rels[i.child_id].add(i.parent_id)
|
||||
rels[c.id].add(p.id)
|
||||
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
@@ -1323,6 +1347,7 @@ class CITypeTemplateManager(object):
|
||||
def _import_attributes(self, type2attributes):
|
||||
attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]]
|
||||
attrs = []
|
||||
references = []
|
||||
for i in copy.deepcopy(attributes):
|
||||
if i.pop('inherited', None):
|
||||
continue
|
||||
@@ -1337,6 +1362,10 @@ class CITypeTemplateManager(object):
|
||||
if not choice_value:
|
||||
i['is_choice'] = False
|
||||
|
||||
if i.get('reference_type_id'):
|
||||
references.append(copy.deepcopy(i))
|
||||
i.pop('reference_type_id')
|
||||
|
||||
attrs.append((i, choice_value))
|
||||
|
||||
attr_id_map = self.__import(Attribute, [i[0] for i in copy.deepcopy(attrs)])
|
||||
@@ -1345,7 +1374,7 @@ class CITypeTemplateManager(object):
|
||||
if choice_value and not i.get('choice_web_hook') and not i.get('choice_other'):
|
||||
AttributeManager.add_choice_values(attr_id_map.get(i['id'], i['id']), i['value_type'], choice_value)
|
||||
|
||||
return attr_id_map
|
||||
return attr_id_map, references
|
||||
|
||||
def _import_ci_types(self, ci_types, attr_id_map):
|
||||
for i in ci_types:
|
||||
@@ -1359,6 +1388,11 @@ class CITypeTemplateManager(object):
|
||||
|
||||
return self.__import(CIType, ci_types)
|
||||
|
||||
def _import_reference_attributes(self, attrs, type_id_map):
|
||||
for attr in attrs:
|
||||
attr['reference_type_id'] = type_id_map.get(attr['reference_type_id'], attr['reference_type_id'])
|
||||
self.__import(Attribute, attrs)
|
||||
|
||||
def _import_ci_type_groups(self, ci_type_groups, type_id_map):
|
||||
_ci_type_groups = copy.deepcopy(ci_type_groups)
|
||||
for i in _ci_type_groups:
|
||||
@@ -1372,6 +1406,10 @@ class CITypeTemplateManager(object):
|
||||
payload = dict(group_id=group_id_map.get(group['id'], group['id']),
|
||||
type_id=type_id_map.get(ci_type['id'], ci_type['id']),
|
||||
order=order)
|
||||
for i in CITypeGroupItem.get_by(type_id=payload['type_id'], to_dict=False):
|
||||
if i.group_id != payload['group_id']:
|
||||
i.soft_delete(flush=True)
|
||||
|
||||
existed = CITypeGroupItem.get_by(group_id=payload['group_id'], type_id=payload['type_id'],
|
||||
first=True, to_dict=False)
|
||||
if existed is None:
|
||||
@@ -1584,13 +1622,15 @@ class CITypeTemplateManager(object):
|
||||
|
||||
import time
|
||||
s = time.time()
|
||||
attr_id_map = self._import_attributes(tpt.get('type2attributes') or {})
|
||||
attr_id_map, references = self._import_attributes(tpt.get('type2attributes') or {})
|
||||
current_app.logger.info('import attributes cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
ci_type_id_map = self._import_ci_types(tpt.get('ci_types') or [], attr_id_map)
|
||||
current_app.logger.info('import ci_types cost: {}'.format(time.time() - s))
|
||||
|
||||
self._import_reference_attributes(references, ci_type_id_map)
|
||||
|
||||
s = time.time()
|
||||
self._import_ci_type_groups(tpt.get('ci_type_groups') or [], ci_type_id_map)
|
||||
current_app.logger.info('import ci_type_groups cost: {}'.format(time.time() - s))
|
||||
@@ -1675,6 +1715,16 @@ class CITypeTemplateManager(object):
|
||||
type_ids.extend(extend_type_ids)
|
||||
ci_types.extend(CITypeManager.get_ci_types(type_ids=extend_type_ids))
|
||||
|
||||
# handle reference type
|
||||
references = Attribute.get_by(only_query=True).join(
|
||||
CITypeAttribute, CITypeAttribute.attr_id == Attribute.id).filter(
|
||||
CITypeAttribute.type_id.in_(type_ids)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
||||
Attribute.reference_type_id.isnot(None))
|
||||
reference_type_ids = list(set([i.reference_type_id for i in references if i.reference_type_id]))
|
||||
if reference_type_ids:
|
||||
type_ids.extend(reference_type_ids)
|
||||
ci_types.extend(CITypeManager.get_ci_types(type_ids=reference_type_ids))
|
||||
|
||||
tpt = dict(
|
||||
ci_types=ci_types,
|
||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||
@@ -1687,6 +1737,7 @@ class CITypeTemplateManager(object):
|
||||
icons=dict()
|
||||
)
|
||||
tpt['ci_type_groups'] = CITypeGroupManager.get(ci_types=tpt['ci_types'], type_ids=type_ids)
|
||||
tpt['ci_type_groups'] = [i for i in tpt['ci_type_groups'] if i.get('ci_types')]
|
||||
|
||||
def get_icon_value(icon):
|
||||
try:
|
||||
|
@@ -14,6 +14,8 @@ class ValueTypeEnum(BaseEnum):
|
||||
JSON = "6"
|
||||
PASSWORD = TEXT
|
||||
LINK = TEXT
|
||||
BOOL = "7"
|
||||
REFERENCE = INT
|
||||
|
||||
|
||||
class ConstraintEnum(BaseEnum):
|
||||
|
@@ -1,8 +1,9 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import copy
|
||||
from collections import defaultdict
|
||||
|
||||
import copy
|
||||
import six
|
||||
import toposort
|
||||
from flask import abort
|
||||
@@ -263,12 +264,12 @@ class PreferenceManager(object):
|
||||
else:
|
||||
views = _views
|
||||
|
||||
view2cr_ids = dict()
|
||||
view2cr_ids = defaultdict(list)
|
||||
name2view = dict()
|
||||
result = dict()
|
||||
name2id = list()
|
||||
for view in views:
|
||||
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
||||
view2cr_ids[view['name']].extend(view['cr_ids'])
|
||||
name2id.append([view['name'], view['id']])
|
||||
name2view[view['name']] = view
|
||||
|
||||
@@ -383,14 +384,22 @@ class PreferenceManager(object):
|
||||
def add_search_option(**kwargs):
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
existed = PreferenceSearchOption.get_by(uid=current_user.uid,
|
||||
name=kwargs.get('name'),
|
||||
prv_id=kwargs.get('prv_id'),
|
||||
ptv_id=kwargs.get('ptv_id'),
|
||||
type_id=kwargs.get('type_id'),
|
||||
)
|
||||
if existed:
|
||||
return abort(400, ErrFormat.preference_search_option_exists)
|
||||
if kwargs['name'] in ('__recent__', '__favor__'):
|
||||
if kwargs['name'] == '__recent__':
|
||||
for i in PreferenceSearchOption.get_by(
|
||||
only_query=True, name=kwargs['name'], uid=current_user.uid).order_by(
|
||||
PreferenceSearchOption.id.desc()).offset(20):
|
||||
i.delete()
|
||||
|
||||
else:
|
||||
existed = PreferenceSearchOption.get_by(uid=current_user.uid,
|
||||
name=kwargs.get('name'),
|
||||
prv_id=kwargs.get('prv_id'),
|
||||
ptv_id=kwargs.get('ptv_id'),
|
||||
type_id=kwargs.get('type_id'),
|
||||
)
|
||||
if existed:
|
||||
return abort(400, ErrFormat.preference_search_option_exists)
|
||||
|
||||
return PreferenceSearchOption.create(**kwargs)
|
||||
|
||||
|
@@ -44,6 +44,8 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_value_not_found = _l("The model's primary key {} does not exist!") # 模型的主键 {} 不存在!
|
||||
unique_key_required = _l("Primary key {} is missing") # 主键字段 {} 缺失
|
||||
ci_is_already_existed = _l("CI already exists!") # CI 已经存在!
|
||||
ci_reference_not_found = _l("{}: CI reference {} does not exist!") # {}: CI引用 {} 不存在!
|
||||
ci_reference_invalid = _l("{}: CI reference {} is illegal!") # {}, CI引用 {} 不合法!
|
||||
relation_constraint = _l("Relationship constraint: {}, verification failed") # 关系约束: {}, 校验失败
|
||||
# 多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!
|
||||
m2m_relation_constraint = _l(
|
||||
@@ -63,6 +65,8 @@ class ErrFormat(CommonErrFormat):
|
||||
ci_exists_and_cannot_delete_inheritance = _l(
|
||||
"The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除继承关系
|
||||
ci_type_inheritance_cannot_delete = _l("The model is inherited and cannot be deleted") # 该模型被继承, 不能删除
|
||||
ci_type_referenced_cannot_delete = _l(
|
||||
"The model is referenced by attribute {} and cannot be deleted") # 该模型被属性 {} 引用, 不能删除
|
||||
|
||||
# 因为关系视图 {} 引用了该模型,不能删除模型
|
||||
ci_relation_view_exists_and_cannot_delete_type = _l(
|
||||
|
@@ -56,7 +56,7 @@ QUERY_CI_BY_ATTR_NAME = """
|
||||
SELECT {0}.ci_id
|
||||
FROM {0}
|
||||
WHERE {0}.attr_id={1:d}
|
||||
AND {0}.value {2}
|
||||
AND ({0}.value {2})
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_ID = """
|
||||
|
@@ -451,6 +451,9 @@ class Search(object):
|
||||
if field_type == ValueTypeEnum.DATE and len(v) == 10:
|
||||
v = "{} 00:00:00".format(v)
|
||||
|
||||
if field_type == ValueTypeEnum.BOOL and "*" not in str(v):
|
||||
v = str(int(v in current_app.config.get('BOOL_TRUE')))
|
||||
|
||||
# in query
|
||||
if v.startswith("(") and v.endswith(")"):
|
||||
_query_sql = self._in_query_handler(attr, v, is_not)
|
||||
|
@@ -7,6 +7,7 @@ import json
|
||||
import re
|
||||
|
||||
import six
|
||||
from flask import current_app
|
||||
|
||||
import api.models.cmdb as model
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
@@ -64,6 +65,7 @@ class ValueTypeMap(object):
|
||||
ValueTypeEnum.DATETIME: str2datetime,
|
||||
ValueTypeEnum.DATE: str2date,
|
||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||
ValueTypeEnum.BOOL: lambda x: x in current_app.config.get('BOOL_TRUE'),
|
||||
}
|
||||
|
||||
serialize = {
|
||||
@@ -74,6 +76,7 @@ class ValueTypeMap(object):
|
||||
ValueTypeEnum.DATE: lambda x: x.strftime("%Y-%m-%d") if not isinstance(x, six.string_types) else x,
|
||||
ValueTypeEnum.DATETIME: lambda x: x.strftime("%Y-%m-%d %H:%M:%S") if not isinstance(x, six.string_types) else x,
|
||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||
ValueTypeEnum.BOOL: lambda x: x in current_app.config.get('BOOL_TRUE'),
|
||||
}
|
||||
|
||||
serialize2 = {
|
||||
@@ -84,6 +87,7 @@ class ValueTypeMap(object):
|
||||
ValueTypeEnum.DATE: lambda x: (x.decode() if not isinstance(x, six.string_types) else x).split()[0],
|
||||
ValueTypeEnum.DATETIME: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||
ValueTypeEnum.BOOL: lambda x: x in current_app.config.get('BOOL_TRUE'),
|
||||
}
|
||||
|
||||
choice = {
|
||||
@@ -105,6 +109,7 @@ class ValueTypeMap(object):
|
||||
'index_{0}'.format(ValueTypeEnum.TIME): model.CIIndexValueText,
|
||||
'index_{0}'.format(ValueTypeEnum.FLOAT): model.CIIndexValueFloat,
|
||||
'index_{0}'.format(ValueTypeEnum.JSON): model.CIValueJson,
|
||||
'index_{0}'.format(ValueTypeEnum.BOOL): model.CIIndexValueInteger,
|
||||
}
|
||||
|
||||
table_name = {
|
||||
@@ -117,6 +122,7 @@ class ValueTypeMap(object):
|
||||
'index_{0}'.format(ValueTypeEnum.TIME): 'c_value_index_texts',
|
||||
'index_{0}'.format(ValueTypeEnum.FLOAT): 'c_value_index_floats',
|
||||
'index_{0}'.format(ValueTypeEnum.JSON): 'c_value_json',
|
||||
'index_{0}'.format(ValueTypeEnum.BOOL): 'c_value_index_integers',
|
||||
}
|
||||
|
||||
es_type = {
|
||||
|
@@ -3,13 +3,13 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import imp
|
||||
|
||||
import copy
|
||||
import jinja2
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from jinja2schema import infer
|
||||
@@ -47,7 +47,7 @@ class AttributeValueManager(object):
|
||||
"""
|
||||
return AttributeCache.get(key)
|
||||
|
||||
def get_attr_values(self, fields, ci_id, ret_key="name", unique_key=None, use_master=False):
|
||||
def get_attr_values(self, fields, ci_id, ret_key="name", unique_key=None, use_master=False, enum_map=None):
|
||||
"""
|
||||
|
||||
:param fields:
|
||||
@@ -55,6 +55,7 @@ class AttributeValueManager(object):
|
||||
:param ret_key: It can be name or alias
|
||||
:param unique_key: primary attribute
|
||||
:param use_master: Only for master-slave read-write separation
|
||||
:param enum_map:
|
||||
:return:
|
||||
"""
|
||||
res = dict()
|
||||
@@ -76,6 +77,12 @@ class AttributeValueManager(object):
|
||||
else:
|
||||
res[field_name] = ValueTypeMap.serialize[attr.value_type](rs[0].value) if rs else None
|
||||
|
||||
if enum_map and field_name in enum_map:
|
||||
if attr.is_list:
|
||||
res[field_name] = [enum_map[field_name].get(i, i) for i in res[field_name]]
|
||||
else:
|
||||
res[field_name] = enum_map[field_name].get(res[field_name], res[field_name])
|
||||
|
||||
if unique_key is not None and attr.id == unique_key.id and rs:
|
||||
res['unique'] = unique_key.name
|
||||
res['unique_alias'] = unique_key.alias
|
||||
@@ -128,14 +135,20 @@ class AttributeValueManager(object):
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.alias, attr.value_type, value)
|
||||
if not attr.is_reference:
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.alias, attr.value_type, value)
|
||||
|
||||
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||
|
||||
else:
|
||||
v = value or None
|
||||
|
||||
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||
attr.is_unique and self._check_is_unique(
|
||||
value_table, attr, ci and ci.id or ci_id, ci and ci.type_id or type_id, v)
|
||||
|
||||
self._check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
|
||||
if attr.is_reference:
|
||||
return v
|
||||
|
||||
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||
v = None
|
||||
@@ -239,7 +252,7 @@ class AttributeValueManager(object):
|
||||
if value.get('op') == "delete":
|
||||
value['v'] = [ValueTypeMap.serialize[attr.value_type](
|
||||
self._deserialize_value(attr.alias, attr.value_type, i))
|
||||
for i in handle_arg_list(value['v'])]
|
||||
for i in handle_arg_list(value['v'])]
|
||||
continue
|
||||
_value = value.get('v') or []
|
||||
else:
|
||||
|
@@ -58,7 +58,7 @@ def _request_messenger(subject, body, tos, sender, payload):
|
||||
|
||||
def notify_send(subject, body, methods, tos, payload=None):
|
||||
payload = payload or {}
|
||||
payload = {k: v or '' for k, v in payload.items()}
|
||||
payload = {k: '' if v is None else v for k, v in payload.items()}
|
||||
subject = Template(subject).render(payload)
|
||||
body = Template(body).render(payload)
|
||||
|
||||
|
@@ -105,6 +105,10 @@ class Attribute(Model):
|
||||
is_password = db.Column(db.Boolean, default=False)
|
||||
is_sortable = db.Column(db.Boolean, default=False)
|
||||
is_dynamic = db.Column(db.Boolean, default=False)
|
||||
is_bool = db.Column(db.Boolean, default=False)
|
||||
|
||||
is_reference = db.Column(db.Boolean, default=False)
|
||||
reference_type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||
|
||||
default = db.Column(db.JSON) # {"default": None}
|
||||
|
||||
|
@@ -20,10 +20,12 @@ from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.const import RelationSourceEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
from api.models.cmdb import AutoDiscoveryCITypeRelation
|
||||
@@ -38,6 +40,7 @@ from api.models.cmdb import CITypeAttribute
|
||||
def ci_cache(ci_id, operate_type, record_id):
|
||||
from api.lib.cmdb.ci import CITriggerManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
@@ -53,7 +56,17 @@ def ci_cache(ci_id, operate_type, record_id):
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get('worker'))
|
||||
|
||||
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
||||
_, enum_map = CITypeAttributeManager.get_attr_names_label_enum(ci_dict.get('_type'))
|
||||
payload = dict()
|
||||
for k, v in ci_dict.items():
|
||||
if k in enum_map:
|
||||
if isinstance(v, list):
|
||||
payload[k] = [enum_map[k].get(i, i) for i in v]
|
||||
else:
|
||||
payload[k] = enum_map[k].get(v, v)
|
||||
else:
|
||||
payload[k] = v
|
||||
CITriggerManager.fire(operate_type, payload, record_id)
|
||||
|
||||
ci_dict and CIRelationManager.build_by_attribute(ci_dict)
|
||||
|
||||
@@ -84,7 +97,7 @@ def batch_ci_cache(ci_ids, ): # only for attribute change index
|
||||
|
||||
@celery.task(name="cmdb.ci_delete", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def ci_delete(ci_id):
|
||||
def ci_delete(ci_id, type_id):
|
||||
current_app.logger.info(ci_id)
|
||||
|
||||
if current_app.config.get("USE_ES"):
|
||||
@@ -99,6 +112,12 @@ def ci_delete(ci_id):
|
||||
adt.update(updated_at=datetime.datetime.now())
|
||||
instance.delete()
|
||||
|
||||
for attr in Attribute.get_by(reference_type_id=type_id, to_dict=False):
|
||||
table = TableMap(attr=attr).table
|
||||
for i in getattr(table, 'get_by')(attr_id=attr.id, value=ci_id, to_dict=False):
|
||||
i.delete()
|
||||
ci_cache(i.ci_id, None, None)
|
||||
|
||||
current_app.logger.info("{0} delete..........".format(ci_id))
|
||||
|
||||
|
||||
@@ -188,7 +207,7 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
for ci in response:
|
||||
try:
|
||||
CIRelationManager.add(ci['_id'], child_id)
|
||||
ci_relation_cache(ci['_id'], child_id)
|
||||
ci_relation_cache(ci['_id'], child_id, None)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(e)
|
||||
finally:
|
||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-06-20 19:12+0800\n"
|
||||
"POT-Creation-Date: 2024-08-20 13:47+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.14.0\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
|
||||
#: api/lib/resp_format.py:7
|
||||
msgid "unauthorized"
|
||||
@@ -169,8 +169,8 @@ msgstr "目前只允许 属性创建人、管理员 删除属性!"
|
||||
#: api/lib/cmdb/resp_format.py:37
|
||||
msgid ""
|
||||
"Attribute field names cannot be built-in fields: id, _id, ci_id, type, "
|
||||
"_type, ci_type"
|
||||
msgstr "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type"
|
||||
"_type, ci_type, ticket_id"
|
||||
msgstr "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type, ci_type, ticket_id"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:39
|
||||
msgid "Predefined value: Other model request parameters are illegal!"
|
||||
@@ -197,286 +197,298 @@ msgid "CI already exists!"
|
||||
msgstr "CI 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:47
|
||||
msgid "{}: CI reference {} does not exist!"
|
||||
msgstr "{}: CI引用 {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:48
|
||||
msgid "{}: CI reference {} is illegal!"
|
||||
msgstr "{}, CI引用 {} 不合法!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:49
|
||||
msgid "Relationship constraint: {}, verification failed"
|
||||
msgstr "关系约束: {}, 校验失败"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:49
|
||||
#: api/lib/cmdb/resp_format.py:51
|
||||
msgid ""
|
||||
"Many-to-many relationship constraint: Model {} <-> {} already has a many-"
|
||||
"to-many relationship!"
|
||||
msgstr "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:52
|
||||
#: api/lib/cmdb/resp_format.py:54
|
||||
msgid "CI relationship: {} does not exist"
|
||||
msgstr "CI关系: {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:55
|
||||
#: api/lib/cmdb/resp_format.py:57
|
||||
msgid "In search expressions, not supported before parentheses: or, not"
|
||||
msgstr "搜索表达式里小括号前不支持: 或、非"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:57
|
||||
#: api/lib/cmdb/resp_format.py:59
|
||||
msgid "Model {} does not exist"
|
||||
msgstr "模型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:58
|
||||
#: api/lib/cmdb/resp_format.py:60
|
||||
msgid "Model {} already exists"
|
||||
msgstr "模型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:59
|
||||
#: api/lib/cmdb/resp_format.py:61
|
||||
msgid "The primary key is undefined or has been deleted"
|
||||
msgstr "主键未定义或者已被删除"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:60
|
||||
#: api/lib/cmdb/resp_format.py:62
|
||||
msgid "Only the creator can delete it!"
|
||||
msgstr "只有创建人才能删除它!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:61
|
||||
#: api/lib/cmdb/resp_format.py:63
|
||||
msgid "The model cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:63
|
||||
#: api/lib/cmdb/resp_format.py:65
|
||||
msgid "The inheritance cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除继承关系"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:65
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
msgid "The model is inherited and cannot be deleted"
|
||||
msgstr "该模型被继承, 不能删除"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:68
|
||||
msgid "The model is referenced by attribute {} and cannot be deleted"
|
||||
msgstr "该模型被属性 {} 引用, 不能删除"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:72
|
||||
msgid ""
|
||||
"The model cannot be deleted because the model is referenced by the "
|
||||
"relational view {}"
|
||||
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
#: api/lib/cmdb/resp_format.py:74
|
||||
msgid "Model group {} does not exist"
|
||||
msgstr "模型分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
#: api/lib/cmdb/resp_format.py:75
|
||||
msgid "Model group {} already exists"
|
||||
msgstr "模型分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:72
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
msgid "Model relationship {} does not exist"
|
||||
msgstr "模型关系 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
#: api/lib/cmdb/resp_format.py:77
|
||||
msgid "Attribute group {} already exists"
|
||||
msgstr "属性分组 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:74
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
msgid "Attribute group {} does not exist"
|
||||
msgstr "属性分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
||||
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:77
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
msgid "The unique constraint already exists!"
|
||||
msgstr "唯一约束已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:79
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
|
||||
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
msgid "Duplicated trigger"
|
||||
msgstr "重复的触发器"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "Duplicated reconciliation rule"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
msgid "Reconciliation rule {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
#: api/lib/cmdb/resp_format.py:100
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "{} 无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
#: api/lib/cmdb/resp_format.py:102
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:100
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:102
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
msgid "Out of range value, the maximum value is 2147483647"
|
||||
msgstr "超过最大值限制, 最大值是2147483647"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
#: api/lib/cmdb/resp_format.py:110
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
#: api/lib/cmdb/resp_format.py:112
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:112
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||
msgstr "自动发现规则的应用不能重复定义!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
#: api/lib/cmdb/resp_format.py:121
|
||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:119
|
||||
#: api/lib/cmdb/resp_format.py:123
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:121
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:123
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
#: api/lib/cmdb/resp_format.py:131
|
||||
msgid "The unique_key method must return a non-empty string!"
|
||||
msgstr "unique_key方法必须返回非空字符串!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:128
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
#: api/lib/cmdb/resp_format.py:134
|
||||
msgid "The list returned by the attributes method cannot be empty!"
|
||||
msgstr "attributes方法返回的list不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
#: api/lib/cmdb/resp_format.py:136
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:136
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
msgid "CI filter authorization is currently not supported or query"
|
||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
#: api/lib/cmdb/resp_format.py:143
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
#: api/lib/cmdb/resp_format.py:144
|
||||
msgid "You do not have permission to operate this CI!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:142
|
||||
#: api/lib/cmdb/resp_format.py:146
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:143
|
||||
#: api/lib/cmdb/resp_format.py:147
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:145
|
||||
#: api/lib/cmdb/resp_format.py:149
|
||||
msgid "Scheduling time format error"
|
||||
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:146
|
||||
#: api/lib/cmdb/resp_format.py:150
|
||||
msgid "CMDB data reconciliation results"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:147
|
||||
#: api/lib/cmdb/resp_format.py:151
|
||||
msgid "Number of {} illegal: {}"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:149
|
||||
#: api/lib/cmdb/resp_format.py:153
|
||||
msgid "Topology view {} already exists"
|
||||
msgstr "拓扑视图 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:150
|
||||
#: api/lib/cmdb/resp_format.py:154
|
||||
msgid "Topology group {} already exists"
|
||||
msgstr "拓扑视图分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:152
|
||||
#: api/lib/cmdb/resp_format.py:156
|
||||
msgid "The group cannot be deleted because the topology view already exists"
|
||||
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||
|
||||
|
@@ -225,6 +225,7 @@ class AutoDiscoveryCIView(APIView):
|
||||
@args_required("type_id")
|
||||
@args_required("adt_id")
|
||||
@args_required("instance")
|
||||
@args_required("unique_value")
|
||||
def post(self):
|
||||
request.values.pop("_key", None)
|
||||
request.values.pop("_secret", None)
|
||||
|
@@ -15,7 +15,7 @@ from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.cmdb.search.ci import search as ci_search
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.utils import get_page
|
||||
@@ -160,7 +160,7 @@ class CISearchView(APIView):
|
||||
use_id_filter = request.values.get("use_id_filter", False) in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
|
||||
s = ci_search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
|
@@ -48,16 +48,21 @@ class CITypeView(APIView):
|
||||
if request.url.endswith("icons"):
|
||||
return self.jsonify(CITypeManager().get_icons())
|
||||
|
||||
q = request.args.get("type_name")
|
||||
q = request.values.get("type_name")
|
||||
type_ids = handle_arg_list(request.values.get("type_ids"))
|
||||
type_ids = type_ids or (type_id and [type_id])
|
||||
if type_ids:
|
||||
ci_types = []
|
||||
for _type_id in type_ids:
|
||||
ci_type = CITypeCache.get(_type_id)
|
||||
if ci_type is None:
|
||||
return abort(404, ErrFormat.ci_type_not_found)
|
||||
|
||||
if type_id is not None:
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type is None:
|
||||
return abort(404, ErrFormat.ci_type_not_found)
|
||||
|
||||
ci_type = ci_type.to_dict()
|
||||
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
|
||||
ci_types = [ci_type]
|
||||
ci_type = ci_type.to_dict()
|
||||
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(_type_id)
|
||||
ci_type['show_name'] = ci_type.get('show_id') and AttributeCache.get(ci_type['show_id']).name
|
||||
ci_type['unique_name'] = ci_type['unique_id'] and AttributeCache.get(ci_type['unique_id']).name
|
||||
ci_types.append(ci_type)
|
||||
elif type_name is not None:
|
||||
ci_type = CITypeCache.get(type_name).to_dict()
|
||||
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1721959219377') format('woff2'),
|
||||
url('iconfont.woff?t=1721959219377') format('woff'),
|
||||
url('iconfont.ttf?t=1721959219377') format('truetype');
|
||||
src: url('iconfont.woff2?t=1725331691589') format('woff2'),
|
||||
url('iconfont.woff?t=1725331691589') format('woff'),
|
||||
url('iconfont.ttf?t=1725331691589') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,258 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.veops-expand:before {
|
||||
content: "\e9b6";
|
||||
}
|
||||
|
||||
.caise-public_cloud:before {
|
||||
content: "\e9b1";
|
||||
}
|
||||
|
||||
.caise-system:before {
|
||||
content: "\e9b2";
|
||||
}
|
||||
|
||||
.caise-IPAM:before {
|
||||
content: "\e9b3";
|
||||
}
|
||||
|
||||
.caise-hyperV:before {
|
||||
content: "\e9b4";
|
||||
}
|
||||
|
||||
.caise-data_center2:before {
|
||||
content: "\e9b5";
|
||||
}
|
||||
|
||||
.caise-hardware:before {
|
||||
content: "\e9ad";
|
||||
}
|
||||
|
||||
.caise-computer:before {
|
||||
content: "\e9ae";
|
||||
}
|
||||
|
||||
.caise-network_devices:before {
|
||||
content: "\e9af";
|
||||
}
|
||||
|
||||
.caise-storage_device:before {
|
||||
content: "\e9b0";
|
||||
}
|
||||
|
||||
.caise-load_balancing:before {
|
||||
content: "\e9ab";
|
||||
}
|
||||
|
||||
.caise-message_queue:before {
|
||||
content: "\e9ac";
|
||||
}
|
||||
|
||||
.caise-websever:before {
|
||||
content: "\e9aa";
|
||||
}
|
||||
|
||||
.caise-middleware:before {
|
||||
content: "\e9a9";
|
||||
}
|
||||
|
||||
.caise-database:before {
|
||||
content: "\e9a7";
|
||||
}
|
||||
|
||||
.caise-business:before {
|
||||
content: "\e9a8";
|
||||
}
|
||||
|
||||
.caise-virtualization:before {
|
||||
content: "\e9a6";
|
||||
}
|
||||
|
||||
.caise-storage_pool:before {
|
||||
content: "\e9a4";
|
||||
}
|
||||
|
||||
.caise-storage_volume1:before {
|
||||
content: "\e9a5";
|
||||
}
|
||||
|
||||
.ciase-aix:before {
|
||||
content: "\e9a3";
|
||||
}
|
||||
|
||||
.caise_pool:before {
|
||||
content: "\e99b";
|
||||
}
|
||||
|
||||
.caise-ip_address:before {
|
||||
content: "\e99c";
|
||||
}
|
||||
|
||||
.caise-computer_room:before {
|
||||
content: "\e99d";
|
||||
}
|
||||
|
||||
.caise-rack:before {
|
||||
content: "\e99e";
|
||||
}
|
||||
|
||||
.caise-pc:before {
|
||||
content: "\e99f";
|
||||
}
|
||||
|
||||
.caise-bandwidth_line:before {
|
||||
content: "\e9a0";
|
||||
}
|
||||
|
||||
.caise-fiber:before {
|
||||
content: "\e9a1";
|
||||
}
|
||||
|
||||
.caise-disk_array:before {
|
||||
content: "\e9a2";
|
||||
}
|
||||
|
||||
.veops-group:before {
|
||||
content: "\e99a";
|
||||
}
|
||||
|
||||
.veops-inheritance:before {
|
||||
content: "\e999";
|
||||
}
|
||||
|
||||
.veops-department:before {
|
||||
content: "\e998";
|
||||
}
|
||||
|
||||
.duose-changwenben1:before {
|
||||
content: "\e997";
|
||||
}
|
||||
|
||||
.duose-quote:before {
|
||||
content: "\e995";
|
||||
}
|
||||
|
||||
.duose-boole:before {
|
||||
content: "\e996";
|
||||
}
|
||||
|
||||
.veops-rule1:before {
|
||||
content: "\e994";
|
||||
}
|
||||
|
||||
.veops-operation_report:before {
|
||||
content: "\e993";
|
||||
}
|
||||
|
||||
.veops-ranking1:before {
|
||||
content: "\e992";
|
||||
}
|
||||
|
||||
.veops-ranking2:before {
|
||||
content: "\e98f";
|
||||
}
|
||||
|
||||
.veops-ranking3:before {
|
||||
content: "\e990";
|
||||
}
|
||||
|
||||
.veops-ranking4:before {
|
||||
content: "\e991";
|
||||
}
|
||||
|
||||
.veops-title5:before {
|
||||
content: "\e98d";
|
||||
}
|
||||
|
||||
.veops-repair1:before {
|
||||
content: "\e98e";
|
||||
}
|
||||
|
||||
.veops-ticket:before {
|
||||
content: "\e988";
|
||||
}
|
||||
|
||||
.veops-model4:before {
|
||||
content: "\e989";
|
||||
}
|
||||
|
||||
.veops-resource21:before {
|
||||
content: "\e98a";
|
||||
}
|
||||
|
||||
.veops-relationship3:before {
|
||||
content: "\e98b";
|
||||
}
|
||||
|
||||
.veops-title6:before {
|
||||
content: "\e98c";
|
||||
}
|
||||
|
||||
.veops-resource11:before {
|
||||
content: "\e97a";
|
||||
}
|
||||
|
||||
.veops-model11:before {
|
||||
content: "\e97b";
|
||||
}
|
||||
|
||||
.veops-relationship1:before {
|
||||
content: "\e97c";
|
||||
}
|
||||
|
||||
.veops-title1:before {
|
||||
content: "\e97d";
|
||||
}
|
||||
|
||||
.veops-title2:before {
|
||||
content: "\e97e";
|
||||
}
|
||||
|
||||
.veops-model2:before {
|
||||
content: "\e97f";
|
||||
}
|
||||
|
||||
.veops-resource2:before {
|
||||
content: "\e980";
|
||||
}
|
||||
|
||||
.veops-warehousing:before {
|
||||
content: "\e981";
|
||||
}
|
||||
|
||||
.veops-relationship2:before {
|
||||
content: "\e982";
|
||||
}
|
||||
|
||||
.veops-title3:before {
|
||||
content: "\e983";
|
||||
}
|
||||
|
||||
.veops-rule2:before {
|
||||
content: "\e984";
|
||||
}
|
||||
|
||||
.veops-model3:before {
|
||||
content: "\e985";
|
||||
}
|
||||
|
||||
.veops-title4:before {
|
||||
content: "\e986";
|
||||
}
|
||||
|
||||
.veops-rule3:before {
|
||||
content: "\e987";
|
||||
}
|
||||
|
||||
.veops-decline:before {
|
||||
content: "\e978";
|
||||
}
|
||||
|
||||
.veops-rise:before {
|
||||
content: "\e979";
|
||||
}
|
||||
|
||||
.caise-data_center:before {
|
||||
content: "\e96f";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,447 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "41681675",
|
||||
"name": "veops-expand",
|
||||
"font_class": "veops-expand",
|
||||
"unicode": "e9b6",
|
||||
"unicode_decimal": 59830
|
||||
},
|
||||
{
|
||||
"icon_id": "41672951",
|
||||
"name": "公有云",
|
||||
"font_class": "caise-public_cloud",
|
||||
"unicode": "e9b1",
|
||||
"unicode_decimal": 59825
|
||||
},
|
||||
{
|
||||
"icon_id": "41672952",
|
||||
"name": "操作系统",
|
||||
"font_class": "caise-system",
|
||||
"unicode": "e9b2",
|
||||
"unicode_decimal": 59826
|
||||
},
|
||||
{
|
||||
"icon_id": "41673309",
|
||||
"name": "IPAM",
|
||||
"font_class": "caise-IPAM",
|
||||
"unicode": "e9b3",
|
||||
"unicode_decimal": 59827
|
||||
},
|
||||
{
|
||||
"icon_id": "41673312",
|
||||
"name": "hyperV",
|
||||
"font_class": "caise-hyperV",
|
||||
"unicode": "e9b4",
|
||||
"unicode_decimal": 59828
|
||||
},
|
||||
{
|
||||
"icon_id": "41673320",
|
||||
"name": "数据中心",
|
||||
"font_class": "caise-data_center2",
|
||||
"unicode": "e9b5",
|
||||
"unicode_decimal": 59829
|
||||
},
|
||||
{
|
||||
"icon_id": "41669141",
|
||||
"name": "硬件设备",
|
||||
"font_class": "caise-hardware",
|
||||
"unicode": "e9ad",
|
||||
"unicode_decimal": 59821
|
||||
},
|
||||
{
|
||||
"icon_id": "41669249",
|
||||
"name": "计算机",
|
||||
"font_class": "caise-computer",
|
||||
"unicode": "e9ae",
|
||||
"unicode_decimal": 59822
|
||||
},
|
||||
{
|
||||
"icon_id": "41669250",
|
||||
"name": "网络设备",
|
||||
"font_class": "caise-network_devices",
|
||||
"unicode": "e9af",
|
||||
"unicode_decimal": 59823
|
||||
},
|
||||
{
|
||||
"icon_id": "41669278",
|
||||
"name": "存储设备",
|
||||
"font_class": "caise-storage_device",
|
||||
"unicode": "e9b0",
|
||||
"unicode_decimal": 59824
|
||||
},
|
||||
{
|
||||
"icon_id": "41659452",
|
||||
"name": "负载均衡",
|
||||
"font_class": "caise-load_balancing",
|
||||
"unicode": "e9ab",
|
||||
"unicode_decimal": 59819
|
||||
},
|
||||
{
|
||||
"icon_id": "41659446",
|
||||
"name": "消息队列",
|
||||
"font_class": "caise-message_queue",
|
||||
"unicode": "e9ac",
|
||||
"unicode_decimal": 59820
|
||||
},
|
||||
{
|
||||
"icon_id": "41659424",
|
||||
"name": "websever",
|
||||
"font_class": "caise-websever",
|
||||
"unicode": "e9aa",
|
||||
"unicode_decimal": 59818
|
||||
},
|
||||
{
|
||||
"icon_id": "41655608",
|
||||
"name": "中间件",
|
||||
"font_class": "caise-middleware",
|
||||
"unicode": "e9a9",
|
||||
"unicode_decimal": 59817
|
||||
},
|
||||
{
|
||||
"icon_id": "41655599",
|
||||
"name": "数据库",
|
||||
"font_class": "caise-database",
|
||||
"unicode": "e9a7",
|
||||
"unicode_decimal": 59815
|
||||
},
|
||||
{
|
||||
"icon_id": "41655591",
|
||||
"name": "业务",
|
||||
"font_class": "caise-business",
|
||||
"unicode": "e9a8",
|
||||
"unicode_decimal": 59816
|
||||
},
|
||||
{
|
||||
"icon_id": "41655550",
|
||||
"name": "虚拟化",
|
||||
"font_class": "caise-virtualization",
|
||||
"unicode": "e9a6",
|
||||
"unicode_decimal": 59814
|
||||
},
|
||||
{
|
||||
"icon_id": "41654680",
|
||||
"name": "存储池",
|
||||
"font_class": "caise-storage_pool",
|
||||
"unicode": "e9a4",
|
||||
"unicode_decimal": 59812
|
||||
},
|
||||
{
|
||||
"icon_id": "41654676",
|
||||
"name": "存储卷",
|
||||
"font_class": "caise-storage_volume1",
|
||||
"unicode": "e9a5",
|
||||
"unicode_decimal": 59813
|
||||
},
|
||||
{
|
||||
"icon_id": "41654608",
|
||||
"name": "aix",
|
||||
"font_class": "ciase-aix",
|
||||
"unicode": "e9a3",
|
||||
"unicode_decimal": 59811
|
||||
},
|
||||
{
|
||||
"icon_id": "41654233",
|
||||
"name": "ip池",
|
||||
"font_class": "caise_pool",
|
||||
"unicode": "e99b",
|
||||
"unicode_decimal": 59803
|
||||
},
|
||||
{
|
||||
"icon_id": "41654237",
|
||||
"name": "ip地址",
|
||||
"font_class": "caise-ip_address",
|
||||
"unicode": "e99c",
|
||||
"unicode_decimal": 59804
|
||||
},
|
||||
{
|
||||
"icon_id": "41654249",
|
||||
"name": "机房",
|
||||
"font_class": "caise-computer_room",
|
||||
"unicode": "e99d",
|
||||
"unicode_decimal": 59805
|
||||
},
|
||||
{
|
||||
"icon_id": "41654271",
|
||||
"name": "机柜",
|
||||
"font_class": "caise-rack",
|
||||
"unicode": "e99e",
|
||||
"unicode_decimal": 59806
|
||||
},
|
||||
{
|
||||
"icon_id": "41654276",
|
||||
"name": "PC",
|
||||
"font_class": "caise-pc",
|
||||
"unicode": "e99f",
|
||||
"unicode_decimal": 59807
|
||||
},
|
||||
{
|
||||
"icon_id": "41654305",
|
||||
"name": "带宽线路",
|
||||
"font_class": "caise-bandwidth_line",
|
||||
"unicode": "e9a0",
|
||||
"unicode_decimal": 59808
|
||||
},
|
||||
{
|
||||
"icon_id": "41654323",
|
||||
"name": "光纤交换机",
|
||||
"font_class": "caise-fiber",
|
||||
"unicode": "e9a1",
|
||||
"unicode_decimal": 59809
|
||||
},
|
||||
{
|
||||
"icon_id": "41654369",
|
||||
"name": "磁盘阵列",
|
||||
"font_class": "caise-disk_array",
|
||||
"unicode": "e9a2",
|
||||
"unicode_decimal": 59810
|
||||
},
|
||||
{
|
||||
"icon_id": "41643869",
|
||||
"name": "veops-group",
|
||||
"font_class": "veops-group",
|
||||
"unicode": "e99a",
|
||||
"unicode_decimal": 59802
|
||||
},
|
||||
{
|
||||
"icon_id": "41637123",
|
||||
"name": "veops-inheritance",
|
||||
"font_class": "veops-inheritance",
|
||||
"unicode": "e999",
|
||||
"unicode_decimal": 59801
|
||||
},
|
||||
{
|
||||
"icon_id": "41570722",
|
||||
"name": "veops-department",
|
||||
"font_class": "veops-department",
|
||||
"unicode": "e998",
|
||||
"unicode_decimal": 59800
|
||||
},
|
||||
{
|
||||
"icon_id": "41437322",
|
||||
"name": "duose-changwenben (1)",
|
||||
"font_class": "duose-changwenben1",
|
||||
"unicode": "e997",
|
||||
"unicode_decimal": 59799
|
||||
},
|
||||
{
|
||||
"icon_id": "41363381",
|
||||
"name": "duose-quote",
|
||||
"font_class": "duose-quote",
|
||||
"unicode": "e995",
|
||||
"unicode_decimal": 59797
|
||||
},
|
||||
{
|
||||
"icon_id": "41363378",
|
||||
"name": "duose-boole",
|
||||
"font_class": "duose-boole",
|
||||
"unicode": "e996",
|
||||
"unicode_decimal": 59798
|
||||
},
|
||||
{
|
||||
"icon_id": "41341306",
|
||||
"name": "veops-rule1",
|
||||
"font_class": "veops-rule1",
|
||||
"unicode": "e994",
|
||||
"unicode_decimal": 59796
|
||||
},
|
||||
{
|
||||
"icon_id": "41337509",
|
||||
"name": "veops-operation_report",
|
||||
"font_class": "veops-operation_report",
|
||||
"unicode": "e993",
|
||||
"unicode_decimal": 59795
|
||||
},
|
||||
{
|
||||
"icon_id": "41335526",
|
||||
"name": "veops-ranking1",
|
||||
"font_class": "veops-ranking1",
|
||||
"unicode": "e992",
|
||||
"unicode_decimal": 59794
|
||||
},
|
||||
{
|
||||
"icon_id": "41335530",
|
||||
"name": "veops-ranking2",
|
||||
"font_class": "veops-ranking2",
|
||||
"unicode": "e98f",
|
||||
"unicode_decimal": 59791
|
||||
},
|
||||
{
|
||||
"icon_id": "41335529",
|
||||
"name": "veops-ranking3",
|
||||
"font_class": "veops-ranking3",
|
||||
"unicode": "e990",
|
||||
"unicode_decimal": 59792
|
||||
},
|
||||
{
|
||||
"icon_id": "41335528",
|
||||
"name": "veops-ranking4",
|
||||
"font_class": "veops-ranking4",
|
||||
"unicode": "e991",
|
||||
"unicode_decimal": 59793
|
||||
},
|
||||
{
|
||||
"icon_id": "41334746",
|
||||
"name": "veops-title5",
|
||||
"font_class": "veops-title5",
|
||||
"unicode": "e98d",
|
||||
"unicode_decimal": 59789
|
||||
},
|
||||
{
|
||||
"icon_id": "41334744",
|
||||
"name": "veops-repair (1)",
|
||||
"font_class": "veops-repair1",
|
||||
"unicode": "e98e",
|
||||
"unicode_decimal": 59790
|
||||
},
|
||||
{
|
||||
"icon_id": "41334753",
|
||||
"name": "veops-ticket",
|
||||
"font_class": "veops-ticket",
|
||||
"unicode": "e988",
|
||||
"unicode_decimal": 59784
|
||||
},
|
||||
{
|
||||
"icon_id": "41334751",
|
||||
"name": "veops-model4",
|
||||
"font_class": "veops-model4",
|
||||
"unicode": "e989",
|
||||
"unicode_decimal": 59785
|
||||
},
|
||||
{
|
||||
"icon_id": "41334752",
|
||||
"name": "veops-resource2 (1)",
|
||||
"font_class": "veops-resource21",
|
||||
"unicode": "e98a",
|
||||
"unicode_decimal": 59786
|
||||
},
|
||||
{
|
||||
"icon_id": "41334750",
|
||||
"name": "veops-relationship3",
|
||||
"font_class": "veops-relationship3",
|
||||
"unicode": "e98b",
|
||||
"unicode_decimal": 59787
|
||||
},
|
||||
{
|
||||
"icon_id": "41334748",
|
||||
"name": "veops-title6",
|
||||
"font_class": "veops-title6",
|
||||
"unicode": "e98c",
|
||||
"unicode_decimal": 59788
|
||||
},
|
||||
{
|
||||
"icon_id": "41334404",
|
||||
"name": "veops-resource1 (1)",
|
||||
"font_class": "veops-resource11",
|
||||
"unicode": "e97a",
|
||||
"unicode_decimal": 59770
|
||||
},
|
||||
{
|
||||
"icon_id": "41334505",
|
||||
"name": "veops-model1 (1)",
|
||||
"font_class": "veops-model11",
|
||||
"unicode": "e97b",
|
||||
"unicode_decimal": 59771
|
||||
},
|
||||
{
|
||||
"icon_id": "41334533",
|
||||
"name": "veops-relationship1",
|
||||
"font_class": "veops-relationship1",
|
||||
"unicode": "e97c",
|
||||
"unicode_decimal": 59772
|
||||
},
|
||||
{
|
||||
"icon_id": "41334535",
|
||||
"name": "veops-title1",
|
||||
"font_class": "veops-title1",
|
||||
"unicode": "e97d",
|
||||
"unicode_decimal": 59773
|
||||
},
|
||||
{
|
||||
"icon_id": "41334572",
|
||||
"name": "veops-title2",
|
||||
"font_class": "veops-title2",
|
||||
"unicode": "e97e",
|
||||
"unicode_decimal": 59774
|
||||
},
|
||||
{
|
||||
"icon_id": "41334579",
|
||||
"name": "veops-model2",
|
||||
"font_class": "veops-model2",
|
||||
"unicode": "e97f",
|
||||
"unicode_decimal": 59775
|
||||
},
|
||||
{
|
||||
"icon_id": "41334590",
|
||||
"name": "veops-resource2",
|
||||
"font_class": "veops-resource2",
|
||||
"unicode": "e980",
|
||||
"unicode_decimal": 59776
|
||||
},
|
||||
{
|
||||
"icon_id": "41334594",
|
||||
"name": "veops-warehousing",
|
||||
"font_class": "veops-warehousing",
|
||||
"unicode": "e981",
|
||||
"unicode_decimal": 59777
|
||||
},
|
||||
{
|
||||
"icon_id": "41334598",
|
||||
"name": "veops-relationship2",
|
||||
"font_class": "veops-relationship2",
|
||||
"unicode": "e982",
|
||||
"unicode_decimal": 59778
|
||||
},
|
||||
{
|
||||
"icon_id": "41334602",
|
||||
"name": "veops-title3",
|
||||
"font_class": "veops-title3",
|
||||
"unicode": "e983",
|
||||
"unicode_decimal": 59779
|
||||
},
|
||||
{
|
||||
"icon_id": "41334603",
|
||||
"name": "veops-rule2",
|
||||
"font_class": "veops-rule2",
|
||||
"unicode": "e984",
|
||||
"unicode_decimal": 59780
|
||||
},
|
||||
{
|
||||
"icon_id": "41334604",
|
||||
"name": "veops-model3",
|
||||
"font_class": "veops-model3",
|
||||
"unicode": "e985",
|
||||
"unicode_decimal": 59781
|
||||
},
|
||||
{
|
||||
"icon_id": "41334725",
|
||||
"name": "veops-title4",
|
||||
"font_class": "veops-title4",
|
||||
"unicode": "e986",
|
||||
"unicode_decimal": 59782
|
||||
},
|
||||
{
|
||||
"icon_id": "41334739",
|
||||
"name": "veops-rule3",
|
||||
"font_class": "veops-rule3",
|
||||
"unicode": "e987",
|
||||
"unicode_decimal": 59783
|
||||
},
|
||||
{
|
||||
"icon_id": "41334004",
|
||||
"name": "veops-decline",
|
||||
"font_class": "veops-decline",
|
||||
"unicode": "e978",
|
||||
"unicode_decimal": 59768
|
||||
},
|
||||
{
|
||||
"icon_id": "41333990",
|
||||
"name": "veops-rise",
|
||||
"font_class": "veops-rise",
|
||||
"unicode": "e979",
|
||||
"unicode_decimal": 59769
|
||||
},
|
||||
{
|
||||
"icon_id": "41143117",
|
||||
"name": "caise-数据中心",
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -61,12 +61,12 @@ export default {
|
||||
)
|
||||
|
||||
// 注册富文本自定义元素
|
||||
const resume = {
|
||||
type: 'attachment',
|
||||
attachmentLabel: '',
|
||||
attachmentValue: '',
|
||||
children: [{ text: '' }], // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
|
||||
}
|
||||
// const resume = {
|
||||
// type: 'attachment',
|
||||
// attachmentLabel: '',
|
||||
// attachmentValue: '',
|
||||
// children: [{ text: '' }], // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
|
||||
// }
|
||||
|
||||
function withAttachment(editor) {
|
||||
// JS 语法
|
||||
|
18
cmdb-ui/src/api/cmdb.js
Normal file
18
cmdb-ui/src/api/cmdb.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function searchCI(params, isShowMessage = true) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/s`,
|
||||
method: 'GET',
|
||||
params: params,
|
||||
isShowMessage
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIType(CITypeName, parameter) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeName}`,
|
||||
method: 'GET',
|
||||
params: parameter
|
||||
})
|
||||
}
|
@@ -84,7 +84,48 @@
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<a-tooltip :title="node.label">
|
||||
{{ node.label }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<a-tooltip :title="node.label">
|
||||
{{ node.label }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</treeselect>
|
||||
<CIReferenceAttr
|
||||
v-if="getAttr(item.property).is_reference && (item.exp === 'is' || item.exp === '~is')"
|
||||
:style="{ width: '175px' }"
|
||||
class="select-filter-component"
|
||||
:referenceTypeId="getAttr(item.property).reference_type_id"
|
||||
:disabled="disabled"
|
||||
v-model="item.value"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="getAttr(item.property).is_bool && (item.exp === 'is' || item.exp === '~is')"
|
||||
v-model="item.value"
|
||||
class="select-filter-component"
|
||||
:style="{ width: '175px' }"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('placeholder2')"
|
||||
>
|
||||
<a-select-option key="1">
|
||||
true
|
||||
</a-select-option>
|
||||
<a-select-option key="0">
|
||||
false
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
@@ -92,15 +133,15 @@
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
v-else-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
id: String(node[0] || ''),
|
||||
label: node[1] ? node[1].label || node[0] : node[0],
|
||||
children: node.children && node.children.length ? node.children : undefined,
|
||||
}
|
||||
}
|
||||
"
|
||||
@@ -199,10 +240,11 @@ import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
import CIReferenceAttr from '../ciReferenceAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
components: { ValueTypeMapIcon, CIReferenceAttr },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
@@ -255,7 +297,7 @@ export default {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
if (_find && (['0', '1', '3', '4', '5'].includes(_find.value_type) || _find.is_reference || _find.is_bool)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
@@ -315,6 +357,9 @@ export default {
|
||||
}
|
||||
return []
|
||||
},
|
||||
getAttr(property) {
|
||||
return this.canSearchPreferenceAttrList.find((item) => item.name === property) || {}
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
@@ -343,4 +388,20 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style lang="less" scoped>
|
||||
.select-filter-component {
|
||||
height: 24px;
|
||||
|
||||
/deep/ .ant-select-selection {
|
||||
height: 24px;
|
||||
background: #f7f8fa;
|
||||
line-height: 24px;
|
||||
border: none;
|
||||
|
||||
.ant-select-selection__rendered {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,18 +15,38 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getPropertyIcon(attr) {
|
||||
switch (attr.value_type) {
|
||||
let valueType = attr.value_type
|
||||
|
||||
if (valueType === '2') {
|
||||
if (attr.is_password) {
|
||||
valueType = '7'
|
||||
} else if (attr.is_link) {
|
||||
valueType = '8'
|
||||
} else if (!attr.is_index) {
|
||||
valueType = '9'
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
valueType === '7' &&
|
||||
attr.is_bool
|
||||
) {
|
||||
valueType = '10'
|
||||
}
|
||||
|
||||
if (
|
||||
valueType === '0' &&
|
||||
attr.is_reference
|
||||
) {
|
||||
valueType = '11'
|
||||
}
|
||||
|
||||
switch (valueType) {
|
||||
case '0':
|
||||
return 'duose-shishu'
|
||||
case '1':
|
||||
return 'duose-fudianshu'
|
||||
case '2':
|
||||
if (attr.is_password) {
|
||||
return 'duose-password'
|
||||
}
|
||||
if (attr.is_link) {
|
||||
return 'duose-link'
|
||||
}
|
||||
return 'duose-wenben'
|
||||
case '3':
|
||||
return 'duose-datetime'
|
||||
@@ -40,6 +60,14 @@ export default {
|
||||
return 'duose-password'
|
||||
case '8':
|
||||
return 'duose-link'
|
||||
case '9':
|
||||
return 'duose-changwenben1'
|
||||
case '10':
|
||||
return 'duose-boole'
|
||||
case '11':
|
||||
return 'duose-quote'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@@ -905,6 +905,84 @@ export const multicolorIconList = [
|
||||
value: 'caise-application',
|
||||
label: '应用',
|
||||
list: [{
|
||||
value: 'caise-disk_array',
|
||||
label: '磁盘阵列'
|
||||
}, {
|
||||
value: 'caise-fiber',
|
||||
label: '光纤交换机'
|
||||
}, {
|
||||
value: 'caise-bandwidth_line',
|
||||
label: '带宽线路'
|
||||
}, {
|
||||
value: 'caise-pc',
|
||||
label: 'PC'
|
||||
}, {
|
||||
value: 'caise-rack',
|
||||
label: '机柜'
|
||||
}, {
|
||||
value: 'caise-computer_room',
|
||||
label: '机房'
|
||||
}, {
|
||||
value: 'caise-ip_address',
|
||||
label: 'ip地址'
|
||||
}, {
|
||||
value: 'caise_pool',
|
||||
label: 'ip池'
|
||||
}, {
|
||||
value: 'ciase-aix',
|
||||
label: 'aix'
|
||||
}, {
|
||||
value: 'caise-storage_volume1',
|
||||
label: '存储卷'
|
||||
}, {
|
||||
value: 'caise-virtualization',
|
||||
label: '虚拟化'
|
||||
}, {
|
||||
value: 'caise-business',
|
||||
label: '业务'
|
||||
}, {
|
||||
value: 'caise-database',
|
||||
label: '数据库'
|
||||
}, {
|
||||
value: 'caise-middleware',
|
||||
label: '中间件'
|
||||
}, {
|
||||
value: 'caise-websever',
|
||||
label: 'websever'
|
||||
}, {
|
||||
value: 'caise-message_queue',
|
||||
label: '消息队列'
|
||||
}, {
|
||||
value: 'caise-load_balancing',
|
||||
label: '负载均衡'
|
||||
}, {
|
||||
value: 'caise-storage_device',
|
||||
label: '存储设备'
|
||||
}, {
|
||||
value: 'caise-network_devices',
|
||||
label: '网络设备'
|
||||
}, {
|
||||
value: 'caise-computer',
|
||||
label: '计算机'
|
||||
}, {
|
||||
value: 'caise-hardware',
|
||||
label: '硬件设备'
|
||||
}, {
|
||||
value: 'caise-data_center2',
|
||||
label: '数据中心'
|
||||
}, {
|
||||
value: 'caise-hyperV',
|
||||
label: 'hyperV'
|
||||
}, {
|
||||
value: 'caise-IPAM',
|
||||
label: 'IPAM'
|
||||
}, {
|
||||
value: 'caise-system',
|
||||
label: '操作系统'
|
||||
}, {
|
||||
value: 'caise-public_cloud',
|
||||
label: '公有云'
|
||||
}, {
|
||||
value: 'caise-data_center',
|
||||
label: '数据中心'
|
||||
}, {
|
||||
|
@@ -257,7 +257,7 @@ export default {
|
||||
const props = {}
|
||||
if (this.$route.name === routeName && selectedIcon) {
|
||||
return <ops-icon type={selectedIcon}></ops-icon>
|
||||
} else if (icon.startsWith('ops-') || icon.startsWith('icon-xianxing') || icon.startsWith('icon-shidi')) {
|
||||
} else if (icon.startsWith('ops-') || icon.startsWith('icon-xianxing') || icon.startsWith('icon-shidi') || icon.startsWith('veops-')) {
|
||||
return <ops-icon type={icon}></ops-icon>
|
||||
} else {
|
||||
typeof (icon) === 'object' ? props.component = icon : props.type = icon
|
||||
|
@@ -61,7 +61,13 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<a-input ref="regInput" :placeholder="$t('regexSelect.placeholder')" :value="current.label" @change="changeLabel">
|
||||
<a-input
|
||||
ref="regInput"
|
||||
:placeholder="$t('regexSelect.placeholder')"
|
||||
:value="current.label"
|
||||
:disabled="disabled"
|
||||
@change="changeLabel"
|
||||
>
|
||||
</a-input>
|
||||
</a-popover>
|
||||
</template>
|
||||
@@ -88,6 +94,10 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
178
cmdb-ui/src/components/ciReferenceAttr/index.vue
Normal file
178
cmdb-ui/src/components/ciReferenceAttr/index.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="reference-attr-select-wrap">
|
||||
<a-select
|
||||
v-bind="$attrs"
|
||||
v-model="selectCIIds"
|
||||
optionFilterProp="title"
|
||||
:mode="isList ? 'multiple' : 'default'"
|
||||
showSearch
|
||||
allowClear
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
class="reference-attr-select"
|
||||
:maxTagCount="2"
|
||||
@dropdownVisibleChange="handleDropdownVisibleChange"
|
||||
@search="handleSearch"
|
||||
@change="handleChange"
|
||||
>
|
||||
<template v-if="!isInit">
|
||||
<a-select-option
|
||||
v-for="(item) in initSelectOption"
|
||||
:key="item.key"
|
||||
:title="item.title"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a-select-option>
|
||||
</template>
|
||||
<a-select-option
|
||||
v-for="(item) in options"
|
||||
:key="item.key"
|
||||
:title="item.title"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { searchCI, getCIType } from '@/api/cmdb'
|
||||
|
||||
export default {
|
||||
name: 'CIReferenceAttr',
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String, Array],
|
||||
default: () => '',
|
||||
},
|
||||
isList: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
referenceShowAttrName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
referenceTypeId: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
initSelectOption: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isInit: false,
|
||||
options: [],
|
||||
innerReferenceShowAttrName: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
referenceTypeId: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler() {
|
||||
this.isInit = false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectCIIds: {
|
||||
get() {
|
||||
if (this.isList) {
|
||||
return this.value || []
|
||||
} else {
|
||||
return this.value ? Number(this.value) : ''
|
||||
}
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val ?? (this.isList ? [] : null))
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async handleDropdownVisibleChange(open) {
|
||||
if (!this.isInit && open && this.referenceTypeId) {
|
||||
this.isInit = true
|
||||
|
||||
if (!this.referenceShowAttrName) {
|
||||
const res = await getCIType(this.referenceTypeId)
|
||||
const ciType = res?.ci_types?.[0]
|
||||
this.innerReferenceShowAttrName = ciType?.show_name || ciType?.unique_name || ''
|
||||
}
|
||||
|
||||
const attrName = this.referenceShowAttrName || this.innerReferenceShowAttrName || ''
|
||||
if (!attrName) {
|
||||
return
|
||||
}
|
||||
|
||||
const res = await searchCI({
|
||||
q: `_type:${this.referenceTypeId}`,
|
||||
fl: attrName,
|
||||
count: 25,
|
||||
})
|
||||
|
||||
let options = res?.result?.map((item) => {
|
||||
return {
|
||||
key: item._id,
|
||||
title: String(item?.[attrName] ?? '')
|
||||
}
|
||||
})
|
||||
|
||||
options = _.uniqBy([...this.initSelectOption, ...options], 'key')
|
||||
|
||||
this.options = options
|
||||
}
|
||||
},
|
||||
|
||||
handleSearch: debounce(async function(v) {
|
||||
const attrName = this.referenceShowAttrName || this.innerReferenceShowAttrName || ''
|
||||
|
||||
if (!attrName || !this.referenceTypeId) {
|
||||
return
|
||||
}
|
||||
|
||||
const res = await searchCI({
|
||||
q: `_type:${this.referenceTypeId}${v ? ',*' + v + '*' : ''}`,
|
||||
fl: attrName,
|
||||
count: v ? 100 : 25,
|
||||
})
|
||||
|
||||
this.options = res?.result?.map((item) => {
|
||||
return {
|
||||
key: item._id,
|
||||
title: String(item?.[attrName] ?? '')
|
||||
}
|
||||
})
|
||||
}, 300),
|
||||
|
||||
handleChange(v) {
|
||||
if (Array.isArray(v) ? !v.length : !v) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.reference-attr-select-wrap {
|
||||
width: 100%;
|
||||
|
||||
.reference-attr-select {
|
||||
width: 100%;
|
||||
|
||||
/deep/ .ant-select-dropdown {
|
||||
z-index: 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -31,7 +31,7 @@ export default {
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@
|
||||
v-for="route in defaultShowRoutes"
|
||||
:key="route.name"
|
||||
@click="() => handleClick(route)"
|
||||
:title="$t(route.meta.title)"
|
||||
>
|
||||
{{ route.meta.title }}
|
||||
</span>
|
||||
@@ -119,7 +118,9 @@ export default {
|
||||
line-height: @layout-header-line-height;
|
||||
display: inline-block;
|
||||
}
|
||||
> span:hover,
|
||||
> span:hover {
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
.top-menu-selected {
|
||||
font-weight: bold;
|
||||
color: @layout-header-font-selected-color;
|
||||
|
@@ -5,15 +5,30 @@
|
||||
<span
|
||||
v-if="hasBackendPermission"
|
||||
@click="handleClick"
|
||||
class="action"
|
||||
style="width: 40px; display: flex; justify-content: center"
|
||||
class="common-settings-btn"
|
||||
>
|
||||
<a-icon type="setting" />
|
||||
<ops-icon class="common-settings-btn-icon" type="veops-setting" />
|
||||
<span class="common-settings-btn-text">{{ $t('settings') }}</span>
|
||||
</span>
|
||||
<span class="locale" @click="changeLang">{{ locale === 'zh' ? 'English' : '中文' }}</span>
|
||||
<a-popover
|
||||
trigger="click"
|
||||
:overlayStyle="{ width: '150px' }"
|
||||
overlayClassName="lang-popover-wrap"
|
||||
placement="bottomRight"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
>
|
||||
<span class="locale">{{ languageList.find((lang) => lang.key === locale).title }}</span>
|
||||
<div class="lang-menu" slot="content">
|
||||
<a
|
||||
v-for="(lang) in languageList"
|
||||
:key="lang.key"
|
||||
:class="['lang-menu-item', lang.key === locale ? 'lang-menu-item_active' : '']"
|
||||
@click="changeLang(lang.key)"
|
||||
>
|
||||
{{ lang.title }}
|
||||
</a>
|
||||
</div>
|
||||
</a-popover>
|
||||
<a-popover
|
||||
:overlayStyle="{ width: '130px' }"
|
||||
placement="bottomRight"
|
||||
overlayClassName="custom-user"
|
||||
>
|
||||
@@ -29,7 +44,7 @@
|
||||
<span>{{ $t('topMenu.logout') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span class="action ant-dropdown-link user-dropdown-menu">
|
||||
<span class="action ant-dropdown-link user-dropdown-menu user-info-wrap">
|
||||
<a-avatar
|
||||
v-if="avatar()"
|
||||
class="avatar"
|
||||
@@ -54,6 +69,20 @@ export default {
|
||||
components: {
|
||||
DocumentLink,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
languageList: [
|
||||
{
|
||||
title: '简中',
|
||||
key: 'zh'
|
||||
},
|
||||
{
|
||||
title: 'EN',
|
||||
key: 'en'
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user', 'locale']),
|
||||
hasBackendPermission() {
|
||||
@@ -81,14 +110,9 @@ export default {
|
||||
handleClick() {
|
||||
this.$router.push('/setting')
|
||||
},
|
||||
changeLang() {
|
||||
if (this.locale === 'zh') {
|
||||
this.SET_LOCALE('en')
|
||||
this.$i18n.locale = 'en'
|
||||
} else {
|
||||
this.SET_LOCALE('zh')
|
||||
this.$i18n.locale = 'zh'
|
||||
}
|
||||
changeLang(lang) {
|
||||
this.SET_LOCALE(lang)
|
||||
this.$i18n.locale = lang
|
||||
this.$nextTick(() => {
|
||||
setDocumentTitle(`${this.$t(this.$route.meta.title)} - ${domTitle}`)
|
||||
})
|
||||
@@ -118,8 +142,88 @@ export default {
|
||||
|
||||
.locale {
|
||||
cursor: pointer;
|
||||
padding: 0 8px;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.lang-popover-wrap {
|
||||
width: 70px;
|
||||
padding: 0px;
|
||||
|
||||
.ant-popover-inner-content {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-wrapper {
|
||||
.common-settings-btn {
|
||||
cursor: pointer;
|
||||
padding: 0px 18px;
|
||||
background-color: #F0F5FF;
|
||||
border-radius: 22px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 8px;
|
||||
|
||||
&-icon {
|
||||
font-size: 12px;
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
&-text {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.commen-settings-btn-text {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lang-menu {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-item {
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
color: #4E5969;
|
||||
|
||||
&_active {
|
||||
color: #2F54EB;
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-info-wrap {
|
||||
.avatar {
|
||||
transition: all 0.2s;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.avatar {
|
||||
border-color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -10,6 +10,7 @@ export default {
|
||||
resourceType: 'Resource Types',
|
||||
trigger: 'Triggers',
|
||||
},
|
||||
settings: 'Common Settings',
|
||||
screen: 'Big Screen',
|
||||
dashboard: 'Dashboard',
|
||||
admin: 'Admin',
|
||||
|
@@ -10,6 +10,7 @@ export default {
|
||||
resourceType: '资源类型',
|
||||
trigger: '触发器',
|
||||
},
|
||||
settings: '通用设置',
|
||||
screen: '大屏',
|
||||
dashboard: '仪表盘',
|
||||
admin: '管理员',
|
||||
|
BIN
cmdb-ui/src/modules/cmdb/assets/no_permission.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/no_permission.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
Binary file not shown.
After Width: | Height: | Size: 444 KiB |
85
cmdb-ui/src/modules/cmdb/components/ciIcon/index.vue
Normal file
85
cmdb-ui/src/modules/cmdb/components/ciIcon/index.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="icon || title"
|
||||
class="ci-icon"
|
||||
:style="{
|
||||
'--size': size + 'px'
|
||||
}"
|
||||
>
|
||||
<template v-if="icon">
|
||||
<img
|
||||
v-if="icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: icon.split('$$')[1],
|
||||
}"
|
||||
:type="icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span
|
||||
class="ci-icon-letter"
|
||||
v-else
|
||||
>
|
||||
<span>
|
||||
{{ title[0].toUpperCase() }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CIIcon',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 如果没有icon, 默认以title 的第一个字符
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: '12'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-icon {
|
||||
font-size: var(--size);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > img {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
}
|
||||
|
||||
&-letter {
|
||||
background-color: #FFFFFF;
|
||||
color: #2f54eb;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
|
||||
& > span {
|
||||
transform-origin: center;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
578
cmdb-ui/src/modules/cmdb/components/ciTable/index.vue
Normal file
578
cmdb-ui/src/modules/cmdb/components/ciTable/index.vue
Normal file
@@ -0,0 +1,578 @@
|
||||
<template>
|
||||
<div class="ci-table-wrap">
|
||||
<ops-table
|
||||
:id="id"
|
||||
border
|
||||
keep-source
|
||||
show-overflow
|
||||
resizable
|
||||
ref="xTable"
|
||||
size="small"
|
||||
:data="data"
|
||||
:loading="loading"
|
||||
:row-config="{ useKey: true, keyField: '_id' }"
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
:checkbox-config="{ reserve: true, highlight: true, range: true }"
|
||||
:edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }"
|
||||
:sort-config="{ remote: true, trigger: 'cell' }"
|
||||
:row-key="true"
|
||||
:column-key="true"
|
||||
:cell-style="getCellStyle"
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:scroll-x="{ enabled: true, gt: 20 }"
|
||||
class="ops-unstripe-table checkbox-hover-table"
|
||||
:custom-config="{ storage: true }"
|
||||
@checkbox-change="onSelectChange"
|
||||
@checkbox-all="onSelectChange"
|
||||
@checkbox-range-end="onSelectRangeEnd"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<vxe-column
|
||||
align="center"
|
||||
type="checkbox"
|
||||
width="60"
|
||||
:fixed="isCheckboxFixed ? 'left' : ''"
|
||||
v-if="showCheckbox"
|
||||
>
|
||||
<template #default="{row}">
|
||||
{{ getRowSeq(row) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-table-column
|
||||
v-for="(col, index) in columns"
|
||||
:key="`${col.field}_${index}`"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:sortable="col.sortable"
|
||||
:edit-render="getColumnsEditRender(col)"
|
||||
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
|
||||
:fixed="col.is_fixed ? 'left' : ''"
|
||||
>
|
||||
<template #header>
|
||||
<span class="vxe-handle">
|
||||
<OpsMoveIcon class="header-move-icon" />
|
||||
<span>{{ col.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password || col.is_bool || col.is_reference" #edit="{ row }">
|
||||
<CIReferenceAttr
|
||||
v-if="col.is_reference"
|
||||
:referenceTypeId="col.reference_type_id"
|
||||
:isList="col.is_list"
|
||||
:referenceShowAttrName="referenceShowAttrNameMap[col.reference_type_id] || ''"
|
||||
:initSelectOption="getInitReferenceSelectOption(row[col.field], col)"
|
||||
v-model="row[col.field]"
|
||||
/>
|
||||
<a-switch
|
||||
v-else-if="col.is_bool"
|
||||
v-model="row[col.field]"
|
||||
/>
|
||||
<vxe-input v-else-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
v-if="col.is_choice"
|
||||
v-model="row[col.field]"
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:showArrow="false"
|
||||
:mode="col.is_list ? 'multiple' : 'default'"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
showSearch
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(choice, idx) in col.filters"
|
||||
:value="choice[0]"
|
||||
:key="'edit_' + col.field + idx"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
...(choice[1] ? choice[1].style : {}),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center'
|
||||
}"
|
||||
>
|
||||
<template v-if="choice[1] && choice[1].icon && choice[1].icon.name">
|
||||
<img
|
||||
v-if="choice[1].icon.id && choice[1].icon.url"
|
||||
:src="`/api/common-setting/v1/file/${choice[1].icon.url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: choice[1].icon.color, marginRight: '5px' }"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
</template>
|
||||
<a-tooltip placement="topLeft" :title="choice[1] ? choice[1].label || choice[0] : choice[0]">
|
||||
<span>{{ choice[1] ? choice[1].label || choice[0] : choice[0] }}</span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice || col.is_reference"
|
||||
#default="{ row }"
|
||||
>
|
||||
<template v-if="col.is_reference" >
|
||||
<a
|
||||
v-for="(ciId) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="ciId"
|
||||
:href="`/cmdb/cidetail/${col.reference_type_id}/${ciId}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ getReferenceAttrValue(ciId, col) }}
|
||||
</a>
|
||||
</template>
|
||||
<span v-else-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
|
||||
<template v-else-if="col.is_link && row[col.field]">
|
||||
<a
|
||||
v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="linkIndex"
|
||||
:href="
|
||||
item.startsWith('http') || item.startsWith('https')
|
||||
? `${item}`
|
||||
: `http://${item}`
|
||||
"
|
||||
target="_blank"
|
||||
>
|
||||
{{ getChoiceValueLabel(col, item) || item }}
|
||||
</a>
|
||||
</template>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
:attr_id="col.attr_id"
|
||||
></PasswordField>
|
||||
<template v-else-if="col.is_choice">
|
||||
<span
|
||||
v-for="value in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="value"
|
||||
:style="getChoiceValueStyle(col, value)"
|
||||
class="column-default-choice"
|
||||
>
|
||||
<img
|
||||
v-if="getChoiceValueIcon(col, value).id && getChoiceValueIcon(col, value).url"
|
||||
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(col, value).url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="getChoiceValueIcon(col, value).name"
|
||||
:style="{ color: getChoiceValueIcon(col, value).color, marginRight: '5px' }"
|
||||
:type="getChoiceValueIcon(col, value).name"
|
||||
/>
|
||||
{{ getChoiceValueLabel(col, value) || value }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-column align="left" field="operate" fixed="right" width="80">
|
||||
<template #header>
|
||||
<span>{{ $t('operation') }}</span>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a @click="openDetail(row.ci_id || row._id)">
|
||||
<a-icon type="unordered-list" />
|
||||
</a>
|
||||
<a-tooltip :title="$t('cmdb.ci.viewRelation')">
|
||||
<a @click="openDetail(row.ci_id || row._id, 'tab_2', '2')">
|
||||
<a-icon type="retweet" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a v-if="showDelete" @click="deleteCI(row)" :style="{ color: 'red' }">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div
|
||||
v-if="loading"
|
||||
class="ci-table-loading"
|
||||
>
|
||||
{{ loadingTip || $t('loading') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<div class="ci-table-loading">{{ loadingTip || $t('loading') }}</div>
|
||||
</template>
|
||||
</ops-table>
|
||||
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import JsonEditor from '../JsonEditor/jsonEditor.vue'
|
||||
import PasswordField from '../passwordField/index.vue'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'CITable',
|
||||
components: {
|
||||
JsonEditor,
|
||||
PasswordField,
|
||||
OpsMoveIcon,
|
||||
CIReferenceAttr
|
||||
},
|
||||
props: {
|
||||
// table ID
|
||||
id: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// table Loading
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// ci 属性列表
|
||||
attrList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// table column
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
passwordValue: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
// 加载提示
|
||||
loadingTip: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否展示复选框
|
||||
showCheckbox: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否展示删除按钮
|
||||
showDelete: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 表格数据
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
referenceShowAttrNameMap: {},
|
||||
referenceCIIdMap: {},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isCheckboxFixed() {
|
||||
const idx = this.columns.findIndex((item) => item.is_fixed)
|
||||
return idx > -1
|
||||
},
|
||||
tableDataWatch() {
|
||||
return {
|
||||
data: this.data,
|
||||
columns: this.columns
|
||||
}
|
||||
},
|
||||
referenceCIIdWatch() {
|
||||
const referenceTypeCol = this.columns?.filter((col) => col?.is_reference && col?.reference_type_id) || []
|
||||
if (!this.data?.length || !referenceTypeCol?.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const ids = []
|
||||
this.data.forEach((row) => {
|
||||
referenceTypeCol.forEach((col) => {
|
||||
if (row[col.field]) {
|
||||
ids.push(...(Array.isArray(row[col.field]) ? row[col.field] : [row[col.field]]))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return _.uniq(ids)
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
columns: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
this.handleReferenceShowAttrName(newVal)
|
||||
}
|
||||
},
|
||||
referenceCIIdWatch: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler() {
|
||||
this.handleReferenceCIIdMap()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getVxetableRef() {
|
||||
return this?.$refs?.['xTable']?.getVxetableRef?.() || null
|
||||
},
|
||||
|
||||
onSelectChange() {
|
||||
const xTable = this.getVxetableRef()
|
||||
const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
|
||||
this.$emit('onSelectChange', records)
|
||||
},
|
||||
|
||||
onSelectRangeEnd({ records }) {
|
||||
this.$emit('onSelectChange', records)
|
||||
},
|
||||
|
||||
getCellStyle({ row, rowIndex, $rowIndex, column, columnIndex, $columnIndex }) {
|
||||
const { property } = column
|
||||
const _find = this.attrList.find((attr) => attr.name === property)
|
||||
if (
|
||||
_find &&
|
||||
_find.option &&
|
||||
_find.option.fontOptions &&
|
||||
row[`${property}`] !== undefined &&
|
||||
row[`${property}`] !== null
|
||||
) {
|
||||
return { ..._find.option.fontOptions }
|
||||
}
|
||||
},
|
||||
|
||||
getColumnsEditRender(col) {
|
||||
const _editRender = {
|
||||
...col.editRender,
|
||||
}
|
||||
|
||||
if (col.value_type === '6') {
|
||||
_editRender.events = { focus: this.handleFocusJson }
|
||||
}
|
||||
|
||||
return _editRender
|
||||
},
|
||||
|
||||
handleFocusJson({ column, row }) {
|
||||
this.$refs.jsonEditor.open(column, row)
|
||||
},
|
||||
|
||||
jsonEditorOk(row, column, jsonData) {
|
||||
this.data.forEach((item) => {
|
||||
if (item._id === row._id) {
|
||||
item[column.property] = JSON.stringify(jsonData)
|
||||
}
|
||||
})
|
||||
this.getVxetableRef().refreshColumn()
|
||||
},
|
||||
|
||||
getChoiceValueStyle(col, colValue) {
|
||||
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.style || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
getChoiceValueIcon(col, colValue) {
|
||||
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.icon || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
getChoiceValueLabel(col, colValue) {
|
||||
const _find = col?.filters?.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.label || ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 开启当前 ci 详情弹窗
|
||||
*/
|
||||
openDetail(id, activeTabKey, ciDetailRelationKey) {
|
||||
this.$emit('openDetail', id, activeTabKey, ciDetailRelationKey)
|
||||
},
|
||||
|
||||
deleteCI(row) {
|
||||
this.$emit('deleteCI', row)
|
||||
},
|
||||
|
||||
getRowSeq(row) {
|
||||
return this.getVxetableRef().getRowSeq(row)
|
||||
},
|
||||
|
||||
async handleReferenceShowAttrName(columns) {
|
||||
const needRequiredCITypeIds = columns?.filter((col) => col?.is_reference && col?.reference_type_id).map((col) => col.reference_type_id) || []
|
||||
if (!needRequiredCITypeIds.length) {
|
||||
this.referenceShowAttrNameMap = {}
|
||||
return
|
||||
}
|
||||
|
||||
const res = await getCITypes({
|
||||
type_ids: needRequiredCITypeIds.join(',')
|
||||
})
|
||||
|
||||
const map = {}
|
||||
res.ci_types.forEach((ciType) => {
|
||||
map[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||
})
|
||||
|
||||
this.referenceShowAttrNameMap = map
|
||||
},
|
||||
|
||||
async handleReferenceCIIdMap() {
|
||||
const referenceTypeCol = this.columns.filter((col) => col?.is_reference && col?.reference_type_id) || []
|
||||
if (!this.data?.length || !referenceTypeCol?.length) {
|
||||
this.referenceCIIdMap = {}
|
||||
return
|
||||
}
|
||||
|
||||
const map = {}
|
||||
this.data.forEach((row) => {
|
||||
referenceTypeCol.forEach((col) => {
|
||||
const ids = Array.isArray(row[col.field]) ? row[col.field] : row[col.field] ? [row[col.field]] : []
|
||||
if (ids.length) {
|
||||
if (!map?.[col.reference_type_id]) {
|
||||
map[col.reference_type_id] = {}
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
map[col.reference_type_id][id] = {}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!Object.keys(map).length) {
|
||||
this.referenceCIIdMap = {}
|
||||
return
|
||||
}
|
||||
|
||||
const allRes = await Promise.all(
|
||||
Object.keys(map).map((key) => {
|
||||
return searchCI({
|
||||
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||
count: 9999
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
allRes.forEach((res) => {
|
||||
res.result.forEach((item) => {
|
||||
if (map?.[item._type]?.[item._id]) {
|
||||
map[item._type][item._id] = item
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.referenceCIIdMap = map
|
||||
},
|
||||
|
||||
getReferenceAttrValue(id, col) {
|
||||
const ci = this?.referenceCIIdMap?.[col?.reference_type_id]?.[id]
|
||||
if (!ci) {
|
||||
return id
|
||||
}
|
||||
|
||||
const attrName = this.referenceShowAttrNameMap?.[col.reference_type_id]
|
||||
return ci?.[attrName] || id
|
||||
},
|
||||
|
||||
getInitReferenceSelectOption(value, col) {
|
||||
const ids = Array.isArray(value) ? value : value ? [value] : []
|
||||
if (!ids.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const map = this?.referenceCIIdMap?.[col?.reference_type_id]
|
||||
const attrName = this.referenceShowAttrNameMap?.[col?.reference_type_id]
|
||||
|
||||
const option = (Array.isArray(value) ? value : [value]).map((id) => {
|
||||
return {
|
||||
key: id,
|
||||
title: map?.[id]?.[attrName] || id
|
||||
}
|
||||
})
|
||||
|
||||
return option
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-table-wrap {
|
||||
.ci-table-loading {
|
||||
width: 100%;
|
||||
line-height: 200px;
|
||||
}
|
||||
|
||||
.header-move-icon {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -3px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.column-default-choice {
|
||||
border-radius: 4px;
|
||||
padding: 1px 5px;
|
||||
margin: 2px;
|
||||
vertical-align: bottom;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-hover-table {
|
||||
/deep/ .vxe-table--body-wrapper {
|
||||
.vxe-checkbox--label {
|
||||
display: inline;
|
||||
padding-left: 0px !important;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.vxe-icon-checkbox-unchecked {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vxe-cell--checkbox {
|
||||
&:hover {
|
||||
.vxe-icon-checkbox-unchecked {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.vxe-checkbox--label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -53,6 +53,7 @@
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="expression"
|
||||
/>
|
||||
<div class="read-ci-tip">{{ $t('cmdb.ciType.ciGrantTip') }}</div>
|
||||
</a-form-model>
|
||||
</template>
|
||||
</CustomRadio>
|
||||
@@ -209,4 +210,10 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style lang="less" scoped>
|
||||
.read-ci-tip {
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
color: #a5a9bc;
|
||||
}
|
||||
</style>
|
||||
|
@@ -0,0 +1,41 @@
|
||||
import i18n from '@/lang'
|
||||
|
||||
export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
export const expList = () => {
|
||||
return [
|
||||
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
|
||||
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
|
||||
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
|
||||
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
|
||||
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
|
||||
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
|
||||
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
|
||||
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = () => {
|
||||
return [
|
||||
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
|
||||
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
|
||||
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
|
||||
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
|
||||
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
|
||||
]
|
||||
}
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
{ value: '2', label: '>=' },
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div :style="{ lineHeight: rowHeight }">
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div v-if="ruleList.length > 1" :style="{ width: '60px', height: rowHeight, position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index !== 0"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': rowHeight, position: 'absolute', top: '-24px' }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '120px', '--custom-height': rowHeight }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
v-if="node.id !== '$count'"
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
class="property-label"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
class="property-label"
|
||||
:style="{ borderBottom: '1px solid #E4E7ED', marginBottom: '8px' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
class="property-label"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '90px', '--custom-height': rowHeight }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="getExpListByProperty(item.property)"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<ValueControls
|
||||
:rule="ruleList[index]"
|
||||
:attrList="canSearchPreferenceAttrList"
|
||||
:disabled="disabled"
|
||||
:curModelAttrList="curModelAttrList"
|
||||
:rowHeight="rowHeight"
|
||||
@change="(value) => handleChangeValue(value, index)"
|
||||
/>
|
||||
<template v-if="!disabled">
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><a-icon type="minus-circle"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
<div class="table-filter-add" v-if="!disabled && ruleList.length === 0">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants.js'
|
||||
import ValueTypeMapIcon from '@/components/CMDBValueTypeMapIcon'
|
||||
import ValueControls from './valueControls.vue'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: {
|
||||
ValueTypeMapIcon,
|
||||
ValueControls
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
curModelAttrList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
rowHeight: '36px',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
ruleTypeList() {
|
||||
return ruleTypeList()
|
||||
},
|
||||
expList() {
|
||||
return expList()
|
||||
},
|
||||
advancedExpList() {
|
||||
return advancedExpList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property === '$count') {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: 'compare', label: this.$t('cmdbFilterComp.compare') }
|
||||
]
|
||||
}
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
...this.advancedExpList
|
||||
]
|
||||
}
|
||||
}
|
||||
return [...this.expList, ...this.advancedExpList]
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleAddRuleAt(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx + 1, 0, {
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
|
||||
handleChangeValue(value, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
_ruleList[index] = value
|
||||
this.$emit('change', _ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 150px;
|
||||
|
||||
&-range-icon {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.property-label {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
.attr-filter-tip {
|
||||
color: #86909C;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
211
cmdb-ui/src/modules/cmdb/components/conditionFilter/index.vue
Normal file
211
cmdb-ui/src/modules/cmdb/components/conditionFilter/index.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div>
|
||||
<Expression
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="false"
|
||||
:curModelAttrList="curModelAttrList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { compareTypeList } from './constants.js'
|
||||
|
||||
import Expression from './expression.vue'
|
||||
|
||||
export default {
|
||||
name: 'AttrFilter',
|
||||
components: {
|
||||
Expression
|
||||
},
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
CITypeIds: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
curModelAttrList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.filter((rule) => rule?.property).map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<div class="control-group">
|
||||
<CIReferenceAttr
|
||||
v-if="getAttr(rule.property).is_reference && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
class="select-filter"
|
||||
:referenceTypeId="getAttr(rule.property).reference_type_id"
|
||||
:value="rule.value"
|
||||
:disabled="disabled"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="getAttr(rule.property).is_bool && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
class="select-filter"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:value="rule.value"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
>
|
||||
<a-select-option key="1">
|
||||
true
|
||||
</a-select-option>
|
||||
<a-select-option key="0">
|
||||
false
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div
|
||||
class="input-group"
|
||||
v-else-if="isChoiceByProperty(rule.property) && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
>
|
||||
<a-select
|
||||
class="select-filter"
|
||||
:style="{ width: '175px' }"
|
||||
showSearch
|
||||
:placeholder="$t('placeholder2')"
|
||||
:disabled="disabled"
|
||||
:value="rule.value"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(node) in getChoiceValueByProperty(rule.property)"
|
||||
:key="String(node[0])"
|
||||
:title="node[1] ? node[1].label || node[0] : node[0]"
|
||||
>
|
||||
<a-tooltip placement="topLeft" :title="node[1] ? node[1].label || node[0] : node[0]" >
|
||||
{{ node[1] ? node[1].label || node[0] : node[0] }}
|
||||
</a-tooltip>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- <treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ '--custom-height': rowHeight }"
|
||||
:value="rule.value"
|
||||
@input="(value) => handleChange('value', value)"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="getChoiceValueByProperty(rule.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[1] ? node[1].label || node[0] : node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
appendToBody
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect> -->
|
||||
</div>
|
||||
<div
|
||||
compact
|
||||
v-else-if="rule.exp === 'range' || rule.exp === '~range'"
|
||||
class="input-group"
|
||||
>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
:placeholder="$t('min')"
|
||||
:disabled="disabled"
|
||||
:value="rule.min"
|
||||
@change="(e) => handleChange('min', e.target.value)"
|
||||
/>
|
||||
<span class="input-group-range-icon">~</span>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
v-model="rule.max"
|
||||
:placeholder="$t('max')"
|
||||
:disabled="disabled"
|
||||
:value="rule.max"
|
||||
@change="(e) => handleChange('max', e.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group" compact v-else-if="rule.exp === 'compare'">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '70px', '--custom-height': rowHeight, 'flex-shrink': 0 }"
|
||||
:value="rule.compareType"
|
||||
@input="(value) => handleChange('compareType', value)"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
appendToBody
|
||||
>
|
||||
</treeselect>
|
||||
<a-input :value="rule.value" @change="(e) => handleChange('value', e.target.value)" class="ops-input"/>
|
||||
</div>
|
||||
<div class="input-group" v-else-if="rule.exp !== 'value' && rule.exp !== '~value'">
|
||||
<a-input
|
||||
:value="rule.value"
|
||||
@change="(e) => handleChange('value', e.target.value)"
|
||||
:placeholder="rule.exp === 'in' || rule.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:disabled="disabled"
|
||||
></a-input>
|
||||
</div>
|
||||
<div v-else :style="{ width: '136px' }"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { compareTypeList } from './constants.js'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ValueControls',
|
||||
components: {
|
||||
CIReferenceAttr,
|
||||
},
|
||||
props: {
|
||||
rule: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
attrList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 当前模型属性
|
||||
curModelAttrList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 行高
|
||||
rowHeight: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
choiceValue() {
|
||||
const val = /\{\{([^}]+)\}\}/g.exec(this?.rule?.value || '')
|
||||
return val ? val?.[1]?.trim() || '' : this?.value?.value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.attrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.attrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChange(key, value) {
|
||||
this.$emit('change', {
|
||||
...this.rule,
|
||||
[key]: value
|
||||
})
|
||||
},
|
||||
getAttr(property) {
|
||||
return this.attrList.find((item) => item.name === property) || {}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.control-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 136px;
|
||||
|
||||
&-range-icon {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-filter {
|
||||
height: 36px;
|
||||
width: 136px;
|
||||
|
||||
/deep/ .ant-select-selection {
|
||||
height: 36px;
|
||||
background: #f7f8fa;
|
||||
line-height: 36px;
|
||||
border: none;
|
||||
|
||||
.ant-select-selection__rendered {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .vue-treeselect__control {
|
||||
background: #f7f8fa;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -113,6 +113,11 @@ export default {
|
||||
this.editor.insertNode(node)
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
const editor = this.editor
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -65,9 +65,7 @@
|
||||
<a><a-icon type="question-circle"/></a>
|
||||
</a-tooltip>
|
||||
</a-input>
|
||||
<a-tooltip :title="$t('reset')">
|
||||
<a-button @click="reset">{{ $t('reset') }}</a-button>
|
||||
</a-tooltip>
|
||||
<a-button @click="reset">{{ $t('reset') }}</a-button>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||
@@ -310,6 +308,11 @@ export default {
|
||||
height: 32px;
|
||||
.search-form-bar-filter {
|
||||
.ops_display_wrapper(transparent);
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
.search-form-bar-filter-icon {
|
||||
color: @primary-color;
|
||||
font-size: 12px;
|
||||
|
@@ -105,7 +105,7 @@ export default {
|
||||
return { method, url, parameters, body, headers, authorization }
|
||||
},
|
||||
setParams(params) {
|
||||
const { method, url, parameters, body, headers, authorization = {} } = params ?? {}
|
||||
const { method = 'GET', url = '', parameters = {}, body = '', headers = {}, authorization = {} } = params ?? {}
|
||||
this.method = method
|
||||
this.url = url
|
||||
this.$refs.Parameters.parameters =
|
||||
|
@@ -62,7 +62,7 @@ const cmdb_en = {
|
||||
desc: 'Reverse order',
|
||||
uniqueKey: 'Unique Identifies',
|
||||
uniqueKeySelect: 'Please select a unique identifier',
|
||||
uniqueKeyTips: 'json/password/computed/choice can not be unique identifies',
|
||||
uniqueKeyTips: 'json/password/computed/selectList can not be unique identifies',
|
||||
notfound: 'Can\'t find what you want?',
|
||||
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
|
||||
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
|
||||
@@ -117,7 +117,7 @@ const cmdb_en = {
|
||||
advancedSettings: 'Advanced Settings',
|
||||
font: 'Font',
|
||||
color: 'Color',
|
||||
choiceValue: 'Predefined value',
|
||||
choiceValue: 'Select List',
|
||||
computedAttribute: 'Computed Attribute',
|
||||
computedAttributeTips: 'The value of this attribute is calculated through an expression constructed from other attributes of the CIType or by executing a piece of code. The reference method of the attribute is: {{ attribute name }}',
|
||||
addAttribute: 'New attribute',
|
||||
@@ -130,6 +130,7 @@ const cmdb_en = {
|
||||
selectAttribute: 'Select Attribute',
|
||||
groupExisted: 'Group name already exists',
|
||||
attributeSortedTips: 'Attributes in other groups cannot be sorted. If you need to sort, please drag them to a custom group first!',
|
||||
attributeSortedTips2: 'Non-inherited attributes cannot be inserted before inherited attributes!',
|
||||
buildinAttribute: 'built-in attributes',
|
||||
expr: 'Expression',
|
||||
code: 'Code',
|
||||
@@ -141,7 +142,7 @@ const cmdb_en = {
|
||||
selectCIType: 'Please select a CMDB CIType',
|
||||
selectCITypeAttributes: 'Please select CIType attributes',
|
||||
selectAttributes: 'Please select attributes',
|
||||
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n Execution entry, returns predefined value\n :return: Returns a list, the type of the value is the same as the type of the attribute\n For example:\n return ["online", "offline"]\n """\n return []',
|
||||
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n Execution entry, returns select list\n :return: Returns a list, the type of the value is the same as the type of the attribute\n For example:\n return ["online", "offline"]\n """\n return []',
|
||||
valueExisted: 'The current value already exists!',
|
||||
addRelation: 'Add Relation',
|
||||
sourceCIType: 'Source CIType',
|
||||
@@ -189,6 +190,14 @@ const cmdb_en = {
|
||||
confirmDeleteTrigger: 'Are you sure to delete this trigger?',
|
||||
int: 'Integer',
|
||||
float: 'Float',
|
||||
longText: 'Long Text',
|
||||
shortText: 'Short Text',
|
||||
shortTextTip: 'Text length <= 128',
|
||||
referenceModel: 'Reference Model',
|
||||
referenceModelTip: 'Please select reference model',
|
||||
referenceModelTip1: 'For quick view of referenced model instances',
|
||||
bool: 'Bool',
|
||||
reference: 'Reference',
|
||||
text: 'Text',
|
||||
datetime: 'DateTime',
|
||||
date: 'Date',
|
||||
@@ -206,7 +215,7 @@ const cmdb_en = {
|
||||
otherGroupTips: 'Non sortable within the other group',
|
||||
filterTips: 'click to show {name}',
|
||||
attributeAssociation: 'Attribute Association',
|
||||
attributeAssociationTip1: 'Automatically establish relationships through the attributes except password, json and multiple of two models',
|
||||
attributeAssociationTip1: 'Automatically establish relationships through attribute values (except password, json, multi-value, long text, boolean, reference) of two models',
|
||||
attributeAssociationTip2: 'Double click to edit',
|
||||
attributeAssociationTip3: 'Two Attributes must be selected',
|
||||
attributeAssociationTip4: 'Please select a attribute from Source CIType',
|
||||
@@ -282,6 +291,34 @@ const cmdb_en = {
|
||||
rule: 'Rule',
|
||||
cascadeAttr: 'Cascade',
|
||||
cascadeAttrTip: 'Cascading attributes note the order',
|
||||
enumValue: 'Value',
|
||||
label: 'Label',
|
||||
valueInputTip: 'Please input value',
|
||||
enumValueTip2: 'Enumeration values cannot be repeated',
|
||||
builtin: 'Built In',
|
||||
department: 'Department',
|
||||
user: 'User',
|
||||
userGroup: 'User Group',
|
||||
departmentTip: 'Scroll down to select all departments in the company structure for common settings',
|
||||
userGroupSelectTip: 'Please select user group',
|
||||
displayValue: 'Display Value',
|
||||
displayValueSelectTip: 'Please select Display Value',
|
||||
departmentCascadeDisplay: 'Cascade Display',
|
||||
filterUsers: 'Filter Users',
|
||||
enum: 'Enum',
|
||||
ciGrantTip: `Filter conditions can be changed dynamically using {{}} referenced variables, currently user variables are supported, such as {{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: 'Please search for resource keywords',
|
||||
resourceSearch: 'Resource Search',
|
||||
recentSearch: 'Recent Search',
|
||||
myCollection: 'My Collection',
|
||||
keyword: 'Keywords',
|
||||
CIType: 'CIType',
|
||||
filterPopoverLabel: 'Filter',
|
||||
conditionFilter: 'Condition Filter',
|
||||
advancedFilter: 'Advanced Filter',
|
||||
saveCondition: 'Save Condition',
|
||||
confirmClear: 'Confirm to clear?',
|
||||
currentPage: 'Current Page'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: 'Unselected',
|
||||
@@ -323,7 +360,7 @@ const cmdb_en = {
|
||||
pleaseSearch: 'Please search',
|
||||
conditionFilter: 'Conditional filtering',
|
||||
attributeDesc: 'Attribute Description',
|
||||
ciSearchTips: '1. JSON/password/link attributes cannot be searched\n2. If the search content includes commas, they need to be escaped\n3. Only index attributes are searched, non-index attributes use conditional filtering',
|
||||
ciSearchTips: '1. JSON/password/link/longText/reference attributes cannot be searched\n2. If the search content includes commas, they need to be escaped\n3. Only index attributes are searched, non-index attributes use conditional filtering',
|
||||
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
|
||||
subCIType: 'Subscription CIType',
|
||||
already: 'already',
|
||||
@@ -367,7 +404,7 @@ const cmdb_en = {
|
||||
tips2: '1. Click to download the template, and users can customize the header of the template file, including model properties and model associations',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
tips3: '2. The red color in the template file represents the model relationship, such as the $Product. Product Name (${Model Name}. {Attribute Name}) column, which establishes the relationship with the product.',
|
||||
tips4: '3. In the download template Excel file, the predefined values of attributes will be set as dropdown options. Please note that due to the limitations of Excel itself, a single dropdown box is limited to a maximum of 255 characters. If it exceeds 255 characters, we will not set the dropdown options for this attribute',
|
||||
tips4: `3. The download template excel file will have the property's drop-down list enumeration configured as a drop-down option. Please note that due to the limitations of Excel itself, a single dropdown box is limited to a maximum of 255 characters. If it exceeds 255 characters, we will not set the dropdown options for this attribute`,
|
||||
tips5: '4. When using Excel templates, please ensure that a single file does not exceed 5000 lines.',
|
||||
},
|
||||
preference: {
|
||||
@@ -548,7 +585,7 @@ class AutoDiscovery(object):
|
||||
"""
|
||||
Define attribute fields
|
||||
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
|
||||
type: String Integer Float Date DateTime Time JSON
|
||||
type: String Integer Float Date DateTime Time JSON Bool Reference
|
||||
For example:
|
||||
return [
|
||||
("ci_type", "String", "CIType name"),
|
||||
@@ -604,6 +641,7 @@ if __name__ == "__main__":
|
||||
attributeDesc: 'Attribute Description',
|
||||
selectRows: 'Select: {rows} items',
|
||||
addRelation: 'Add Relation',
|
||||
viewRelation: 'View Relation',
|
||||
all: 'All',
|
||||
batchUpdate: 'Batch Update',
|
||||
batchUpdateConfirm: 'Are you sure you want to make batch updates?',
|
||||
@@ -626,7 +664,7 @@ if __name__ == "__main__":
|
||||
tips4: 'At least one field must be selected',
|
||||
tips5: 'Search name | alias',
|
||||
tips6: 'Speed up retrieval, full-text search possible, no need to use conditional filtering\n\n json/link/password currently does not support indexing \n\nText characters longer than 190 cannot be indexed',
|
||||
tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value',
|
||||
tips7: 'Whether to configure a select list',
|
||||
tips8: 'Multiple values, such as intranet IP',
|
||||
tips9: 'For front-end only',
|
||||
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
|
||||
|
@@ -62,7 +62,7 @@ const cmdb_zh = {
|
||||
desc: '倒序',
|
||||
uniqueKey: '唯一标识',
|
||||
uniqueKeySelect: '请选择唯一标识',
|
||||
uniqueKeyTips: 'json、密码、计算属性、预定义值属性不能作为唯一标识',
|
||||
uniqueKeyTips: 'json、密码、计算属性、下拉列表属性不能作为唯一标识',
|
||||
notfound: '找不到想要的?',
|
||||
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
|
||||
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
|
||||
@@ -117,7 +117,7 @@ const cmdb_zh = {
|
||||
advancedSettings: '高级设置',
|
||||
font: '字体',
|
||||
color: '颜色',
|
||||
choiceValue: '预定义值',
|
||||
choiceValue: '下拉列表',
|
||||
computedAttribute: '计算属性',
|
||||
computedAttributeTips: '该属性的值是通过模型的其它属性构建的表达式或者执行一段代码的方式计算而来,属性的引用方法为: {{ 属性名 }}',
|
||||
addAttribute: '新增属性',
|
||||
@@ -130,6 +130,7 @@ const cmdb_zh = {
|
||||
selectAttribute: '添加属性',
|
||||
groupExisted: '分组名称已存在',
|
||||
attributeSortedTips: '其他分组中的属性不能进行排序,如需排序请先拖至自定义的分组!',
|
||||
attributeSortedTips2: '非继承属性不能插入到继承属性前!',
|
||||
buildinAttribute: '内置字段',
|
||||
expr: '表达式',
|
||||
code: '代码',
|
||||
@@ -141,7 +142,7 @@ const cmdb_zh = {
|
||||
selectCIType: '请选择CMDB模型',
|
||||
selectCITypeAttributes: '请选择模型属性',
|
||||
selectAttributes: '请选择属性',
|
||||
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n 执行入口, 返回预定义值\n :return: 返回一个列表, 值的类型同属性的类型\n 例如:\n return ["在线", "下线"]\n """\n return []',
|
||||
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n 执行入口, 返回下拉列表\n :return: 返回一个列表, 值的类型同属性的类型\n 例如:\n return ["在线", "下线"]\n """\n return []',
|
||||
valueExisted: '当前值已存在!',
|
||||
addRelation: '新增关系',
|
||||
sourceCIType: '源模型',
|
||||
@@ -189,6 +190,14 @@ const cmdb_zh = {
|
||||
confirmDeleteTrigger: '确认删除该触发器吗?',
|
||||
int: '整数',
|
||||
float: '浮点数',
|
||||
longText: '长文本',
|
||||
shortText: '短文本',
|
||||
shortTextTip: '文本长度 <= 128',
|
||||
referenceModel: '引用模型',
|
||||
referenceModelTip: '请选择引用模型',
|
||||
referenceModelTip1: '用于快捷查看引用模型实例',
|
||||
bool: '布尔',
|
||||
reference: '引用',
|
||||
text: '文本',
|
||||
datetime: '日期时间',
|
||||
date: '日期',
|
||||
@@ -206,7 +215,7 @@ const cmdb_zh = {
|
||||
otherGroupTips: '其他分组属性不可排序',
|
||||
filterTips: '点击可仅查看{name}属性',
|
||||
attributeAssociation: '属性关联',
|
||||
attributeAssociationTip1: '通过2个模型的属性值(除密码、json、多值)来自动建立关系',
|
||||
attributeAssociationTip1: '通过2个模型的属性值(除密码、json、多值、长文本、布尔、引用)来自动建立关系',
|
||||
attributeAssociationTip2: '双击可编辑',
|
||||
attributeAssociationTip3: '属性关联必须选择两个属性',
|
||||
attributeAssociationTip4: '请选择原模型属性',
|
||||
@@ -282,6 +291,34 @@ const cmdb_zh = {
|
||||
rule: '规则',
|
||||
cascadeAttr: '级联',
|
||||
cascadeAttrTip: '级联属性注意顺序',
|
||||
enumValue: '枚举值',
|
||||
label: '标签',
|
||||
valueInputTip: '请输入枚举值',
|
||||
enumValueTip2: '枚举值不能重复',
|
||||
builtin: '内置',
|
||||
department: '部门',
|
||||
user: '用户',
|
||||
userGroup: '用户组',
|
||||
departmentTip: '下拉选择为通用设置公司架构里的所有部门',
|
||||
userGroupSelectTip: '请选择用户组',
|
||||
displayValue: '展示值',
|
||||
displayValueSelectTip: '请选择展示值',
|
||||
departmentCascadeDisplay: '部门级联显示',
|
||||
filterUsers: '筛选用户',
|
||||
enum: '枚举',
|
||||
ciGrantTip: `筛选条件可使用{{}}引用变量实现动态变化,目前支持用户变量,如{{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: '请搜索资源关键字',
|
||||
resourceSearch: '资源搜索',
|
||||
recentSearch: '最近搜索',
|
||||
myCollection: '我的收藏',
|
||||
keyword: '关键字',
|
||||
CIType: '模型',
|
||||
filterPopoverLabel: '条件过滤',
|
||||
conditionFilter: '条件过滤',
|
||||
advancedFilter: '高级筛选',
|
||||
saveCondition: '保存条件',
|
||||
confirmClear: '确认清空?',
|
||||
currentPage: '当前页'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
@@ -323,7 +360,7 @@ const cmdb_zh = {
|
||||
pleaseSearch: '请查找',
|
||||
conditionFilter: '条件过滤',
|
||||
attributeDesc: '属性说明',
|
||||
ciSearchTips: '1. json、密码、链接属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤',
|
||||
ciSearchTips: '1. json、密码、链接、长文本、引用属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤',
|
||||
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
|
||||
subCIType: '订阅模型',
|
||||
already: '已',
|
||||
@@ -366,7 +403,7 @@ const cmdb_zh = {
|
||||
tips2: '1. 点击下载模板,用户可以自定义模板文件的表头,包括模型属性、模型关联',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
tips3: '2. 模板文件中红色为模型关系,如$产品.产品名(${模型名}.{属性名})这一列就可建立和产品之间的关系',
|
||||
tips4: '3. 下载模板excel文件中会将属性的预定义值置为下拉选项,请注意,受excel本身的限制,单个下拉框限制了最多255个字符,如果超过255个字符,我们不会设置该属性的下拉选项',
|
||||
tips4: '3. 下载模板excel文件中会将属性的下拉列表枚举配置置为下拉选项,请注意,受excel本身的限制,单个下拉框限制了最多255个字符,如果超过255个字符,我们不会设置该属性的下拉选项',
|
||||
tips5: '4. 在使用excel模板时,请确保单个文件不超过5000行',
|
||||
},
|
||||
preference: {
|
||||
@@ -547,7 +584,7 @@ class AutoDiscovery(object):
|
||||
"""
|
||||
Define attribute fields
|
||||
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
|
||||
type: String Integer Float Date DateTime Time JSON
|
||||
type: String Integer Float Date DateTime Time JSON Bool Reference
|
||||
For example:
|
||||
return [
|
||||
("ci_type", "String", "CIType name"),
|
||||
@@ -603,6 +640,7 @@ if __name__ == "__main__":
|
||||
attributeDesc: '查看属性配置',
|
||||
selectRows: '选取:{rows} 项',
|
||||
addRelation: '添加关系',
|
||||
viewRelation: '查看关系',
|
||||
all: '全部',
|
||||
batchUpdate: '批量修改',
|
||||
batchUpdateConfirm: '确认要批量修改吗?',
|
||||
@@ -625,7 +663,7 @@ if __name__ == "__main__":
|
||||
tips4: '必须至少选择一个字段',
|
||||
tips5: '搜索 名称 | 别名',
|
||||
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json、链接、密码目前不支持建索引 \n\n文本字符长度超过190不能建索引',
|
||||
tips7: '表现形式是下拉框, 值必须在预定义值里',
|
||||
tips7: '是否配置下拉列表',
|
||||
tips8: '多值, 比如内网IP',
|
||||
tips9: '仅针对前端',
|
||||
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
|
||||
|
@@ -54,7 +54,7 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/resourcesearch',
|
||||
name: 'cmdb_resource_search',
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search', keepAlive: false },
|
||||
component: () => import('../views/resource_search/index.vue')
|
||||
component: () => import('../views/resource_search_2/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/adc',
|
||||
@@ -102,7 +102,7 @@ const genCmdbRoutes = async () => {
|
||||
name: 'cmdb_ci_type',
|
||||
component: RouteView,
|
||||
redirect: '/cmdb/ci_type',
|
||||
meta: { title: 'cmdb.menu.backendManage', icon: 'setting', permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], },
|
||||
meta: { title: 'cmdb.menu.backendManage', icon: 'veops-setting2', selectedIcon: 'veops-setting2', permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], },
|
||||
children: [
|
||||
{
|
||||
path: '/cmdb/customdashboard',
|
||||
@@ -117,11 +117,10 @@ const genCmdbRoutes = async () => {
|
||||
meta: { title: 'cmdb.menu.serviceTreeDefine', keepAlive: false, icon: 'ops-cmdb-preferencerelation', selectedIcon: 'ops-cmdb-preferencerelation-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/modelrelation',
|
||||
name: 'model_relation',
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/model_relation/index'),
|
||||
meta: { title: 'cmdb.menu.citypeRelation', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' }
|
||||
path: '/cmdb/discovery',
|
||||
name: 'discovery',
|
||||
component: () => import('../views/discovery/index'),
|
||||
meta: { title: 'cmdb.menu.ad', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/operationhistory',
|
||||
@@ -130,19 +129,20 @@ const genCmdbRoutes = async () => {
|
||||
component: () => import('../views/operation_history/index'),
|
||||
meta: { title: 'cmdb.menu.operationHistory', keepAlive: false, icon: 'ops-cmdb-operation', selectedIcon: 'ops-cmdb-operation-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/modelrelation',
|
||||
name: 'model_relation',
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/model_relation/index'),
|
||||
meta: { title: 'cmdb.menu.citypeRelation', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/relationtype',
|
||||
name: 'relation_type',
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/relation_type/index'),
|
||||
meta: { title: 'cmdb.menu.relationType', keepAlive: false, icon: 'ops-cmdb-relationtype', selectedIcon: 'ops-cmdb-relationtype-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/discovery',
|
||||
name: 'discovery',
|
||||
component: () => import('../views/discovery/index'),
|
||||
meta: { title: 'cmdb.menu.ad', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' }
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@@ -4,13 +4,16 @@ export const valueTypeMap = () => {
|
||||
return {
|
||||
'0': i18n.t('cmdb.ciType.int'),
|
||||
'1': i18n.t('cmdb.ciType.float'),
|
||||
'2': i18n.t('cmdb.ciType.text'),
|
||||
'2': i18n.t('cmdb.ciType.shortText'),
|
||||
'3': i18n.t('cmdb.ciType.datetime'),
|
||||
'4': i18n.t('cmdb.ciType.date'),
|
||||
'5': i18n.t('cmdb.ciType.time'),
|
||||
'6': 'JSON',
|
||||
'7': i18n.t('cmdb.ciType.password'),
|
||||
'8': i18n.t('cmdb.ciType.link')
|
||||
'8': i18n.t('cmdb.ciType.link'),
|
||||
'9': i18n.t('cmdb.ciType.longText'),
|
||||
'10': i18n.t('cmdb.ciType.bool'),
|
||||
'11': i18n.t('cmdb.ciType.reference'),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -91,12 +91,16 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
|
||||
value_type: attr.value_type,
|
||||
sortable: !!attr.is_sortable,
|
||||
filters: attr.is_choice ? attr.choice_value : null,
|
||||
choice_builtin: null,
|
||||
width: Math.min(Math.max(100, ...data.map(item => strLength(item[attr.name]))), 350),
|
||||
is_link: attr.is_link,
|
||||
is_password: attr.is_password,
|
||||
is_list: attr.is_list,
|
||||
is_choice: attr.is_choice,
|
||||
is_fixed: attr.is_fixed,
|
||||
is_bool: attr.is_bool,
|
||||
is_reference: attr.is_reference,
|
||||
reference_type_id: attr.reference_type_id
|
||||
})
|
||||
}
|
||||
|
||||
@@ -137,6 +141,10 @@ export const getPropertyStyle = (attr) => {
|
||||
export const getPropertyIcon = (attr) => {
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
if (attr.is_reference) {
|
||||
return 'duose-quote'
|
||||
}
|
||||
|
||||
return 'duose-shishu'
|
||||
case '1':
|
||||
return 'duose-fudianshu'
|
||||
@@ -147,6 +155,9 @@ export const getPropertyIcon = (attr) => {
|
||||
if (attr.is_link) {
|
||||
return 'duose-link'
|
||||
}
|
||||
if (attr.is_index === false) {
|
||||
return 'duose-changwenben1'
|
||||
}
|
||||
return 'duose-wenben'
|
||||
case '3':
|
||||
return 'duose-datetime'
|
||||
@@ -157,12 +168,52 @@ export const getPropertyIcon = (attr) => {
|
||||
case '6':
|
||||
return 'duose-json'
|
||||
case '7':
|
||||
if (attr.is_bool) {
|
||||
return 'duose-boole'
|
||||
}
|
||||
return 'duose-password'
|
||||
case '8':
|
||||
return 'duose-link'
|
||||
case '9':
|
||||
return 'duose-changwenben1'
|
||||
case '10':
|
||||
return 'duose-boole'
|
||||
case '11':
|
||||
return 'duose-quote'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const getPropertyType = (attr) => {
|
||||
if (attr.is_password) {
|
||||
return '7'
|
||||
}
|
||||
if (attr.is_link) {
|
||||
return '8'
|
||||
}
|
||||
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
if (attr.is_reference) {
|
||||
return '11'
|
||||
}
|
||||
return '0'
|
||||
case '2':
|
||||
if (!attr.is_index) {
|
||||
return '9'
|
||||
}
|
||||
return '2'
|
||||
case '7':
|
||||
if (attr.is_bool) {
|
||||
return '10'
|
||||
}
|
||||
return '7'
|
||||
default:
|
||||
return attr?.value_type ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => {
|
||||
const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc'])
|
||||
if (!_tempData.length) {
|
||||
|
@@ -74,205 +74,24 @@
|
||||
</div>
|
||||
</SearchForm>
|
||||
<CiDetailDrawer ref="detail" :typeId="typeId" />
|
||||
<ops-table
|
||||
:id="`cmdb-ci-${typeId}`"
|
||||
border
|
||||
keep-source
|
||||
show-overflow
|
||||
resizable
|
||||
|
||||
<CITable
|
||||
ref="xTable"
|
||||
size="small"
|
||||
:row-config="{ useKey: true, keyField: '_id' }"
|
||||
:height="tableHeight"
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
:id="`cmdb-ci-${typeId}`"
|
||||
:loading="loading"
|
||||
:attrList="preferenceAttrList"
|
||||
:columns="columns"
|
||||
:passwordValue="passwordValue"
|
||||
:data="instanceList"
|
||||
@checkbox-change="onSelectChange"
|
||||
@checkbox-all="onSelectChange"
|
||||
@checkbox-range-end="onSelectRangeEnd"
|
||||
:checkbox-config="{ reserve: true, highlight: true, range: true }"
|
||||
:height="tableHeight"
|
||||
@onSelectChange="onSelectChange"
|
||||
@edit-closed="handleEditClose"
|
||||
@edit-actived="handleEditActived"
|
||||
:edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }"
|
||||
:sort-config="{ remote: true, trigger: 'cell' }"
|
||||
@sort-change="handleSortCol"
|
||||
:row-key="true"
|
||||
:column-key="true"
|
||||
:cell-style="getCellStyle"
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:scroll-x="{ enabled: true, gt: 0 }"
|
||||
class="ops-unstripe-table checkbox-hover-table"
|
||||
:custom-config="{ storage: true }"
|
||||
>
|
||||
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''">
|
||||
<template #default="{row}">
|
||||
{{ getRowSeq(row) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-table-column
|
||||
v-for="(col, index) in columns"
|
||||
:key="`${col.field}_${index}`"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:sortable="col.sortable"
|
||||
:edit-render="getColumnsEditRender(col)"
|
||||
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
|
||||
:fixed="col.is_fixed ? 'left' : ''"
|
||||
>
|
||||
<template #header>
|
||||
<span class="vxe-handle">
|
||||
<OpsMoveIcon
|
||||
style="width: 17px; height: 17px; display: none; position: absolute; left: -3px; top: 12px"
|
||||
/>
|
||||
<span>{{ col.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:v-bind="$t('placeholder2')"
|
||||
v-if="col.is_choice"
|
||||
:showArrow="false"
|
||||
:mode="col.is_list ? 'multiple' : 'default'"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
showSearch
|
||||
>
|
||||
<a-select-option
|
||||
:value="choice[0]"
|
||||
:key="'edit_' + col.field + idx"
|
||||
v-for="(choice, idx) in col.filters"
|
||||
>
|
||||
<span
|
||||
:style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }"
|
||||
>
|
||||
<template v-if="choice[1] && choice[1].icon && choice[1].icon.name">
|
||||
<img
|
||||
v-if="choice[1].icon.id && choice[1].icon.url"
|
||||
:src="`/api/common-setting/v1/file/${choice[1].icon.url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: choice[1].icon.color, marginRight: '5px' }"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
</template>
|
||||
<span>{{ choice[0] }}</span>
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
#default="{ row }"
|
||||
>
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
|
||||
<template v-else-if="col.is_link && row[col.field]">
|
||||
<a
|
||||
v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="linkIndex"
|
||||
:href="
|
||||
item.startsWith('http') || item.startsWith('https')
|
||||
? `${item}`
|
||||
: `http://${item}`
|
||||
"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item }}
|
||||
</a>
|
||||
</template>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
:attr_id="col.attr_id"
|
||||
></PasswordField>
|
||||
<template v-else-if="col.is_choice">
|
||||
<template v-if="col.is_list">
|
||||
<span
|
||||
v-for="value in row[col.field]"
|
||||
:key="value"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px',
|
||||
verticalAlign: 'bottom',
|
||||
...getChoiceValueStyle(col, value),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="getChoiceValueIcon(col, value).id && getChoiceValueIcon(col, value).url"
|
||||
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(col, value).url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="getChoiceValueIcon(col, value).name"
|
||||
:style="{ color: getChoiceValueIcon(col, value).color, marginRight: '5px' }"
|
||||
:type="getChoiceValueIcon(col, value).name"
|
||||
/>{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
<span
|
||||
v-else
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px 0',
|
||||
verticalAlign: 'bottom',
|
||||
...getChoiceValueStyle(col, row[col.field]),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="getChoiceValueIcon(col, row[col.field]).id && getChoiceValueIcon(col, row[col.field]).url"
|
||||
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(col, row[col.field]).url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="getChoiceValueIcon(col, row[col.field]).name"
|
||||
:style="{ color: getChoiceValueIcon(col, row[col.field]).color, marginRight: '5px' }"
|
||||
:type="getChoiceValueIcon(col, row[col.field]).name"
|
||||
/>
|
||||
{{ row[col.field] }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-column align="left" field="operate" fixed="right" width="80">
|
||||
<template #header>
|
||||
<span>{{ $t('operation') }}</span>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a @click="$refs.detail.create(row.ci_id || row._id)">
|
||||
<a-icon type="unordered-list" />
|
||||
</a>
|
||||
<a-tooltip :title="$t('cmdb.ci.addRelation')">
|
||||
<a @click="$refs.detail.create(row.ci_id || row._id, 'tab_2', '2')">
|
||||
<a-icon type="retweet" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a @click="deleteCI(row)" :style="{ color: 'red' }">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div v-if="loading" style="height: 200px; line-height: 200px">{{ $t('loading') }}</div>
|
||||
<div v-else>
|
||||
<img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</ops-table>
|
||||
@openDetail="openDetail"
|
||||
@deleteCI="deleteCI"
|
||||
/>
|
||||
|
||||
<div :style="{ textAlign: 'right', marginTop: '4px' }">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
@@ -304,7 +123,6 @@
|
||||
</a-pagination>
|
||||
</div>
|
||||
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<ci-rollback-form ref="ciRollbackForm" @batchRollbackAsync="batchRollbackAsync($event)" :ciIds="selectedRowKeys" />
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
@@ -324,7 +142,6 @@ import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||||
import CreateInstanceForm from './modules/CreateInstanceForm'
|
||||
import CiDetailDrawer from './modules/ciDetailDrawer.vue'
|
||||
import EditAttrsPopover from './modules/editAttrsPopover'
|
||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getSubscribeAttributes, subscribeCIType, subscribeTreeView } from '@/modules/cmdb/api/preference'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
@@ -332,15 +149,14 @@ import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { getCITableColumns } from '../../utils/helper'
|
||||
import { intersection } from '@/utils/functions/set'
|
||||
import PasswordField from '../../components/passwordField/index.vue'
|
||||
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
|
||||
import PreferenceSearch from '../../components/preferenceSearch/preferenceSearch.vue'
|
||||
import MetadataDrawer from './modules/MetadataDrawer.vue'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import { getAttrPassword } from '../../api/CITypeAttr'
|
||||
import CiRollbackForm from './modules/ciRollbackForm.vue'
|
||||
import { CIBaselineRollback } from '../../api/history'
|
||||
import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'InstanceList',
|
||||
@@ -348,24 +164,18 @@ export default {
|
||||
SearchForm,
|
||||
CreateInstanceForm,
|
||||
CiDetailDrawer,
|
||||
JsonEditor,
|
||||
PasswordField,
|
||||
EditAttrsPopover,
|
||||
BatchDownload,
|
||||
PreferenceSearch,
|
||||
MetadataDrawer,
|
||||
CMDBGrant,
|
||||
OpsMoveIcon,
|
||||
CiRollbackForm,
|
||||
CITable
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
isCheckboxFixed() {
|
||||
const idx = this.columns.findIndex((item) => item.is_fixed)
|
||||
return idx > -1
|
||||
},
|
||||
tableHeight() {
|
||||
// if (this.selectedRowKeys && this.selectedRowKeys.length) {
|
||||
// return this.windowHeight - 246
|
||||
@@ -551,12 +361,7 @@ export default {
|
||||
const subscribed = await getSubscribeAttributes(this.typeId)
|
||||
this.preferenceAttrList = subscribed.attributes // All columns that have been subscribed
|
||||
},
|
||||
onSelectChange() {
|
||||
const xTable = this.$refs.xTable.getVxetableRef()
|
||||
const records = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
},
|
||||
onSelectRangeEnd({ records }) {
|
||||
onSelectChange(records) {
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
},
|
||||
reloadData() {
|
||||
@@ -612,7 +417,7 @@ export default {
|
||||
|
||||
async openBatchDownload() {
|
||||
this.$refs.batchDownload.open({
|
||||
preferenceAttrList: this.preferenceAttrList,
|
||||
preferenceAttrList: this.preferenceAttrList.filter((attr) => !attr?.is_reference),
|
||||
ciTypeName: this.$route.meta.title || this.$route.meta.name,
|
||||
})
|
||||
},
|
||||
@@ -660,13 +465,12 @@ export default {
|
||||
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress') + '...'
|
||||
const payload = {}
|
||||
Object.keys(values).forEach((key) => {
|
||||
if (values[key] || values[key] === 0) {
|
||||
payload[key] = values[key]
|
||||
}
|
||||
// Field values support blanking
|
||||
// There are currently field values that do not support blanking and will be returned by the backend.
|
||||
if (values[key] === undefined || values[key] === null) {
|
||||
payload[key] = null
|
||||
} else {
|
||||
payload[key] = values[key]
|
||||
}
|
||||
})
|
||||
this.$refs.create.visible = false
|
||||
@@ -820,28 +624,6 @@ export default {
|
||||
await this.loadPreferenceAttrList()
|
||||
await this.loadTableData()
|
||||
},
|
||||
getColumnsEditRender(col) {
|
||||
const _editRender = {
|
||||
...col.editRender,
|
||||
}
|
||||
if (col.value_type === '6') {
|
||||
_editRender.events = { focus: this.handleFocusJson }
|
||||
}
|
||||
return _editRender
|
||||
},
|
||||
handleFocusJson({ column, row }) {
|
||||
this.$refs.jsonEditor.open(column, row)
|
||||
},
|
||||
jsonEditorOk(row, column, jsonData) {
|
||||
// The backend writes data at different speeds. You can modify the table data directly without pulling the interface.
|
||||
// this.reloadData()
|
||||
this.instanceList.forEach((item) => {
|
||||
if (item._id === row._id) {
|
||||
item[column.property] = JSON.stringify(jsonData)
|
||||
}
|
||||
})
|
||||
this.$refs.xTable.getVxetableRef().refreshColumn()
|
||||
},
|
||||
onShowSizeChange(current, pageSize) {
|
||||
this.pageSize = pageSize
|
||||
if (this.currentPage === 1) {
|
||||
@@ -903,23 +685,6 @@ export default {
|
||||
)
|
||||
})
|
||||
},
|
||||
// tableFilterChangeEvent({ column, property, values, datas, filterList, $event }) {
|
||||
// console.log(111)
|
||||
// },
|
||||
getChoiceValueStyle(col, colValue) {
|
||||
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.style || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
getChoiceValueIcon(col, colValue) {
|
||||
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.icon || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
handleEditActived() {
|
||||
this.isEditActive = true
|
||||
const passwordCol = this.columns.filter((col) => col.is_password)
|
||||
@@ -945,19 +710,6 @@ export default {
|
||||
this.lastEditCiId = row._id
|
||||
})
|
||||
},
|
||||
getCellStyle({ row, rowIndex, $rowIndex, column, columnIndex, $columnIndex }) {
|
||||
const { property } = column
|
||||
const _find = this.preferenceAttrList.find((attr) => attr.name === property)
|
||||
if (
|
||||
_find &&
|
||||
_find.option &&
|
||||
_find.option.fontOptions &&
|
||||
row[`${property}`] !== undefined &&
|
||||
row[`${property}`] !== null
|
||||
) {
|
||||
return { ..._find.option.fontOptions }
|
||||
}
|
||||
},
|
||||
getQAndSort() {
|
||||
const fuzzySearch = this.$refs['search'].fuzzySearch || ''
|
||||
const expression = this.$refs['search'].expression || ''
|
||||
@@ -1055,8 +807,8 @@ export default {
|
||||
this.visible = false
|
||||
}
|
||||
},
|
||||
getRowSeq(row) {
|
||||
return this.$refs.xTable.getVxetableRef().getRowSeq(row)
|
||||
openDetail(id, activeTabKey, ciDetailRelationKey) {
|
||||
this.$refs.detail.create(id, activeTabKey, ciDetailRelationKey)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1075,33 +827,4 @@ export default {
|
||||
overflow: auto;
|
||||
margin-bottom: -24px;
|
||||
}
|
||||
.checkbox-hover-table {
|
||||
/deep/ .vxe-table--body-wrapper {
|
||||
.vxe-checkbox--label {
|
||||
display: inline;
|
||||
padding-left: 0px !important;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.vxe-icon-checkbox-unchecked {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vxe-cell--checkbox {
|
||||
&:hover {
|
||||
.vxe-icon-checkbox-unchecked {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.vxe-checkbox--label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -35,7 +35,7 @@
|
||||
<a-select v-model="parentsForm[item.name].attr">
|
||||
<a-select-option
|
||||
:title="attr.alias || attr.name"
|
||||
v-for="attr in item.attributes"
|
||||
v-for="attr in filterAttributes(item.attributes)"
|
||||
:key="attr.name"
|
||||
:value="attr.name"
|
||||
>
|
||||
@@ -87,11 +87,32 @@
|
||||
</a-col>
|
||||
<a-col :span="showListOperation(list.name) ? 10 : 13">
|
||||
<a-form-item>
|
||||
<CIReferenceAttr
|
||||
v-if="getAttr(list.name).is_reference"
|
||||
:referenceTypeId="getAttr(list.name).reference_type_id"
|
||||
:isList="getAttr(list.name).is_list"
|
||||
v-decorator="[
|
||||
list.name,
|
||||
{
|
||||
initialValue: getAttr(list.name).is_list ? [] : ''
|
||||
}
|
||||
]"
|
||||
/>
|
||||
<a-switch
|
||||
v-else-if="getAttr(list.name).is_bool"
|
||||
v-decorator="[
|
||||
list.name,
|
||||
{
|
||||
valuePropName: 'checked',
|
||||
initialValue: false
|
||||
}
|
||||
]"
|
||||
/>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-if="getFieldType(list.name).split('%%')[0] === 'select'"
|
||||
v-else-if="getFieldType(list.name).split('%%')[0] === 'select'"
|
||||
:mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'"
|
||||
showSearch
|
||||
allowClear
|
||||
@@ -107,25 +128,27 @@
|
||||
v-if="choice[1] && choice[1].icon && choice[1].icon.name"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
{{ choice[0] }}
|
||||
<a-tooltip placement="topLeft" :title="choice[1] ? choice[1].label || choice[0] : choice[0]" >
|
||||
{{ choice[1] ? choice[1].label || choice[0] : choice[0] }}
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input-number
|
||||
v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
|
||||
style="width: 100%"
|
||||
v-if="getFieldType(list.name) === 'input_number'"
|
||||
v-else-if="getFieldType(list.name) === 'input_number'"
|
||||
/>
|
||||
<a-date-picker
|
||||
v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
|
||||
style="width: 100%"
|
||||
:format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
:valueFormat="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
|
||||
v-else-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
|
||||
:showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
<a-input
|
||||
v-if="getFieldType(list.name) === 'input'"
|
||||
v-else-if="getFieldType(list.name) === 'input'"
|
||||
@focus="(e) => handleFocusInput(e, list)"
|
||||
v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
|
||||
/>
|
||||
@@ -156,6 +179,7 @@ import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import { valueTypeMap } from '../../../utils/const'
|
||||
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
||||
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'CreateInstanceForm',
|
||||
@@ -164,6 +188,7 @@ export default {
|
||||
ElOption: Option,
|
||||
JsonEditor,
|
||||
CreateInstanceFormByGroup,
|
||||
CIReferenceAttr
|
||||
},
|
||||
props: {
|
||||
typeIdFromRelation: {
|
||||
@@ -261,6 +286,11 @@ export default {
|
||||
}
|
||||
Object.keys(values).forEach((k) => {
|
||||
const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
|
||||
if (_tempFind.is_reference) {
|
||||
values[k] = values[k] ? values[k] : null
|
||||
}
|
||||
|
||||
if (
|
||||
_tempFind.value_type === '3' &&
|
||||
values[k] &&
|
||||
@@ -309,6 +339,11 @@ export default {
|
||||
|
||||
Object.keys(values).forEach((k) => {
|
||||
const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
|
||||
if (_tempFind.is_reference) {
|
||||
values[k] = values[k] ? values[k] : null
|
||||
}
|
||||
|
||||
if (
|
||||
_tempFind.value_type === '3' &&
|
||||
values[k] &&
|
||||
@@ -426,6 +461,9 @@ export default {
|
||||
}
|
||||
return 'input'
|
||||
},
|
||||
getAttr(name) {
|
||||
return this.attributeList.find((item) => item.name === name) ?? {}
|
||||
},
|
||||
getSelectFieldOptions(name) {
|
||||
const _find = this.attributeList.find((item) => item.name === name)
|
||||
if (_find) {
|
||||
@@ -487,7 +525,12 @@ export default {
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
},
|
||||
filterAttributes(attributes) {
|
||||
return attributes.filter((attr) => {
|
||||
return !attr.is_bool && !attr.is_reference
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -498,7 +541,7 @@ export default {
|
||||
}
|
||||
.ant-drawer-body {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 110px);
|
||||
height: calc(100vh - 110px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -79,6 +79,8 @@
|
||||
import XEUtils from 'xe-utils'
|
||||
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { valueTypeMap } from '@/modules/cmdb/utils/const'
|
||||
import { getPropertyType } from '@/modules/cmdb/utils/helper'
|
||||
|
||||
export default {
|
||||
name: 'MetadataDrawer',
|
||||
data() {
|
||||
@@ -187,12 +189,7 @@ export default {
|
||||
this.loading = true
|
||||
const { attributes = [] } = await getCITypeAttributesByName(this.typeId)
|
||||
this.tableData = attributes.map((attr) => {
|
||||
if (attr.is_password) {
|
||||
attr.value_type = '7'
|
||||
}
|
||||
if (attr.is_link) {
|
||||
attr.value_type = '8'
|
||||
}
|
||||
attr.value_type = getPropertyType(attr)
|
||||
return attr
|
||||
})
|
||||
this.loading = false
|
||||
|
@@ -1,9 +1,19 @@
|
||||
<template>
|
||||
<span :id="`ci-detail-attr-${attr.name}`">
|
||||
<span v-if="!isEdit || attr.value_type === '6'">
|
||||
<template v-if="attr.is_reference" >
|
||||
<a
|
||||
v-for="(ciId) in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
|
||||
:key="ciId"
|
||||
:href="`/cmdb/cidetail/${attr.reference_type_id}/${ciId}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ attr.referenceShowAttrNameMap ? attr.referenceShowAttrNameMap[ciId] || ciId : ciId }}
|
||||
</a>
|
||||
</template>
|
||||
<PasswordField
|
||||
:style="{ display: 'inline-block' }"
|
||||
v-if="attr.is_password && ci[attr.name]"
|
||||
v-else-if="attr.is_password && ci[attr.name]"
|
||||
:ci_id="ci._id"
|
||||
:attr_id="attr.id"
|
||||
></PasswordField>
|
||||
@@ -32,7 +42,7 @@
|
||||
:style="{ color: getChoiceValueIcon(attr, value).color, marginRight: '5px' }"
|
||||
:type="getChoiceValueIcon(attr, value).name"
|
||||
/>
|
||||
{{ value }}</span
|
||||
{{ getChoiceValueLabel(attr, value) || value }}</span
|
||||
>
|
||||
</template>
|
||||
<span
|
||||
@@ -56,7 +66,7 @@
|
||||
:style="{ color: getChoiceValueIcon(attr, ci[attr.name]).color, marginRight: '5px' }"
|
||||
:type="getChoiceValueIcon(attr, ci[attr.name]).name"
|
||||
/>
|
||||
{{ ci[attr.name] }}
|
||||
{{ getChoiceValueLabel(attr, ci[attr.name]) || ci[attr.name] }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="attr.is_list">
|
||||
@@ -67,6 +77,29 @@
|
||||
<template v-else>
|
||||
<a-form :form="form">
|
||||
<a-form-item label="" :colon="false">
|
||||
<CIReferenceAttr
|
||||
v-if="attr.is_reference"
|
||||
:referenceTypeId="attr.reference_type_id"
|
||||
:isList="attr.is_list"
|
||||
:referenceShowAttrName="attr.showAttrName"
|
||||
:initSelectOption="getInitReferenceSelectOption(attr)"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
}
|
||||
]"
|
||||
/>
|
||||
<a-switch
|
||||
v-else-if="attr.is_bool"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required }],
|
||||
valuePropName: 'checked',
|
||||
}
|
||||
]"
|
||||
/>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[
|
||||
@@ -76,7 +109,7 @@
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-if="attr.is_choice"
|
||||
v-else-if="attr.is_choice"
|
||||
:mode="attr.is_list ? 'multiple' : 'default'"
|
||||
showSearch
|
||||
allowClear
|
||||
@@ -101,7 +134,7 @@
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
</template>
|
||||
{{ choice[0] }}
|
||||
{{ choice[1] ? choice[1].label || choice[0] : choice[0] }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
@@ -157,10 +190,11 @@ import { updateCI } from '@/modules/cmdb/api/ci'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import PasswordField from '../../../components/passwordField/index.vue'
|
||||
import { getAttrPassword } from '../../../api/CITypeAttr'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'CiDetailAttrContent',
|
||||
components: { JsonEditor, PasswordField },
|
||||
components: { JsonEditor, PasswordField, CIReferenceAttr },
|
||||
props: {
|
||||
ci: {
|
||||
type: Object,
|
||||
@@ -209,7 +243,7 @@ export default {
|
||||
}
|
||||
this.isEdit = true
|
||||
this.$nextTick(async () => {
|
||||
if (this.attr.is_list && !this.attr.is_choice) {
|
||||
if (this.attr.is_list && !this.attr.is_choice && !this.attr.is_reference) {
|
||||
this.form.setFieldsValue({
|
||||
[`${this.attr.name}`]: Array.isArray(this.ci[this.attr.name])
|
||||
? this.ci[this.attr.name].join(',')
|
||||
@@ -237,6 +271,10 @@ export default {
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('updateSuccess'))
|
||||
this.$emit('updateCIByself', { [`${this.attr.name}`]: newData }, this.attr.name)
|
||||
|
||||
if (this.attr.is_reference) {
|
||||
this.$emit('refreshReferenceAttr')
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit('refresh', this.attr.name)
|
||||
@@ -280,9 +318,28 @@ export default {
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
getChoiceValueLabel(col, colValue) {
|
||||
const _find = col.choice_value.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.label || ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
|
||||
getName(name) {
|
||||
return name ?? ''
|
||||
},
|
||||
|
||||
getInitReferenceSelectOption(attr) {
|
||||
const option = Object.keys(attr?.referenceShowAttrNameMap || {}).map((key) => {
|
||||
return {
|
||||
key: Number(key),
|
||||
title: attr?.referenceShowAttrNameMap?.[key] ?? ''
|
||||
}
|
||||
})
|
||||
return option
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -38,6 +38,16 @@
|
||||
resizable
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<template #reference_default="{ row, column }">
|
||||
<a
|
||||
v-for="(id) in (column.params.attr.is_list ? row[column.field] : [row[column.field]])"
|
||||
:key="id"
|
||||
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ id }}
|
||||
</a>
|
||||
</template>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm
|
||||
arrowPointAtCenter
|
||||
@@ -85,6 +95,16 @@
|
||||
resizable
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<template #reference_default="{ row, column }">
|
||||
<a
|
||||
v-for="(id) in (column.params.attr.is_list ? row[column.field] : [row[column.field]])"
|
||||
:key="id"
|
||||
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ id }}
|
||||
</a>
|
||||
</template>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm
|
||||
arrowPointAtCenter
|
||||
@@ -213,6 +233,7 @@ export default {
|
||||
this.firstCIJsonAttr[item._type].forEach((attr) => {
|
||||
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
|
||||
})
|
||||
this.formatCI(item, this.firstCIColumns)
|
||||
if (item.ci_type in firstCIs) {
|
||||
firstCIs[item.ci_type].push(item)
|
||||
} else {
|
||||
@@ -231,6 +252,7 @@ export default {
|
||||
this.secondCIJsonAttr[item._type].forEach((attr) => {
|
||||
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
|
||||
})
|
||||
this.formatCI(item, this.secondCIColumns)
|
||||
if (item.ci_type in secondCIs) {
|
||||
secondCIs[item.ci_type].push(item)
|
||||
} else {
|
||||
@@ -241,6 +263,26 @@ export default {
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
|
||||
formatCI(ci, columns) {
|
||||
Object.keys(ci).forEach((key) => {
|
||||
const attr = columns?.[ci?._type]?.find((item) => item?.params?.attr?.name === key)?.params?.attr
|
||||
if (attr?.is_choice && attr?.choice_value?.length) {
|
||||
if (attr?.is_list) {
|
||||
ci[key] = ci[key].map((value) => {
|
||||
const label = attr?.choice_value?.find((choice) => choice?.[0] === value)?.[1]?.label
|
||||
return label || ci[key]
|
||||
})
|
||||
} else {
|
||||
const label = attr?.choice_value?.find((choice) => choice?.[0] === ci[key])?.[1]?.label
|
||||
ci[key] = label || ci[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return ci
|
||||
},
|
||||
|
||||
async getParentCITypes() {
|
||||
const res = await getCITypeParent(this.typeId)
|
||||
this.parentCITypes = res.parents
|
||||
@@ -258,7 +300,22 @@ export default {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
const column = {
|
||||
key: 'p_' + attr.id,
|
||||
field: attr.name,
|
||||
title: attr.alias,
|
||||
minWidth: '100px',
|
||||
params: {
|
||||
attr
|
||||
},
|
||||
}
|
||||
if (attr.is_reference) {
|
||||
column.slots = {
|
||||
default: 'reference_default'
|
||||
}
|
||||
}
|
||||
columns.push(column)
|
||||
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
@@ -299,7 +356,22 @@ export default {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
|
||||
const column = {
|
||||
key: 'c_' + attr.id,
|
||||
field: attr.name,
|
||||
title: attr.alias,
|
||||
minWidth: '100px',
|
||||
params: {
|
||||
attr
|
||||
},
|
||||
}
|
||||
if (attr.is_reference) {
|
||||
column.slots = {
|
||||
default: 'reference_default'
|
||||
}
|
||||
}
|
||||
columns.push(column)
|
||||
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@
|
||||
:key="attr.name"
|
||||
v-for="attr in group.attributes"
|
||||
>
|
||||
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
|
||||
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" @refreshReferenceAttr="handleReferenceAttr" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -137,7 +137,7 @@ import _ from 'lodash'
|
||||
import { Descriptions, DescriptionsItem } from 'element-ui'
|
||||
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
|
||||
import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import { getCIById, searchCI } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
@@ -244,9 +244,78 @@ export default {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
.then((res) => {
|
||||
this.attributeGroups = res
|
||||
|
||||
this.handleReferenceAttr()
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
|
||||
async handleReferenceAttr() {
|
||||
const map = {}
|
||||
this.attributeGroups.forEach((group) => {
|
||||
group.attributes.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id && this.ci[attr.name]) {
|
||||
const ids = Array.isArray(this.ci[attr.name]) ? this.ci[attr.name] : this.ci[attr.name] ? [this.ci[attr.name]] : []
|
||||
if (ids.length) {
|
||||
if (!map?.[attr.reference_type_id]) {
|
||||
map[attr.reference_type_id] = {}
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
map[attr.reference_type_id][id] = {}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!Object.keys(map).length) {
|
||||
return
|
||||
}
|
||||
|
||||
const ciTypesRes = await getCITypes({
|
||||
type_ids: Object.keys(map).join(',')
|
||||
})
|
||||
const showAttrNameMap = {}
|
||||
ciTypesRes.ci_types.forEach((ciType) => {
|
||||
showAttrNameMap[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||
})
|
||||
|
||||
const allRes = await Promise.all(
|
||||
Object.keys(map).map((key) => {
|
||||
return searchCI({
|
||||
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||
count: 9999
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const ciNameMap = {}
|
||||
allRes.forEach((res) => {
|
||||
res.result.forEach((item) => {
|
||||
ciNameMap[item._id] = item
|
||||
})
|
||||
})
|
||||
|
||||
const newAttrGroups = _.cloneDeep(this.attributeGroups)
|
||||
|
||||
newAttrGroups.forEach((group) => {
|
||||
group.attributes.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id) {
|
||||
attr.showAttrName = showAttrNameMap?.[attr?.reference_type_id] || ''
|
||||
|
||||
const referenceShowAttrNameMap = {}
|
||||
const referenceCIIds = this.ci[attr.name];
|
||||
(Array.isArray(referenceCIIds) ? referenceCIIds : referenceCIIds ? [referenceCIIds] : []).forEach((id) => {
|
||||
referenceShowAttrNameMap[id] = ciNameMap?.[id]?.[attr.showAttrName] ?? id
|
||||
})
|
||||
attr.referenceShowAttrNameMap = referenceShowAttrNameMap
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.$set(this, 'attributeGroups', newAttrGroups)
|
||||
},
|
||||
|
||||
async getCI() {
|
||||
await getCIById(this.ciId)
|
||||
.then((res) => {
|
||||
|
@@ -16,24 +16,40 @@
|
||||
:key="attr.name + attr_idx"
|
||||
>
|
||||
<a-form-item :label="attr.alias || attr.name" :colon="false">
|
||||
<CIReferenceAttr
|
||||
v-if="attr.is_reference"
|
||||
:referenceTypeId="attr.reference_type_id"
|
||||
:isList="attr.is_list"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.is_list ? [] : ''
|
||||
}
|
||||
]"
|
||||
/>
|
||||
<a-switch
|
||||
v-else-if="attr.is_bool"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: false }],
|
||||
valuePropName: 'checked',
|
||||
initialValue: attr.default ? Boolean(attr.default.default) : false
|
||||
}
|
||||
]"
|
||||
/>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
initialValue:
|
||||
attr.default && attr.default.default
|
||||
? attr.is_list
|
||||
? Array.isArray(attr.default.default)
|
||||
? attr.default.default
|
||||
: attr.default.default.split(',')
|
||||
: attr.default.default
|
||||
: attr.is_list ? [] : null,
|
||||
initialValue: getChoiceDefault(attr),
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-if="attr.is_choice"
|
||||
v-else-if="attr.is_choice"
|
||||
:mode="attr.is_list ? 'multiple' : 'default'"
|
||||
showSearch
|
||||
allowClear
|
||||
@@ -56,7 +72,9 @@
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
</template>
|
||||
{{ choice[0] }}
|
||||
<a-tooltip placement="topLeft" :title="choice[1] ? choice[1].label || choice[0] : choice[0]">
|
||||
{{ choice[1] ? choice[1].label || choice[0] : choice[0] }}
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
@@ -67,7 +85,7 @@
|
||||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : '',
|
||||
initialValue: attr.default && attr.default.default ? Array.isArray(attr.default.default) ? attr.default.default.join(',') : attr.default.default : '',
|
||||
},
|
||||
]"
|
||||
>
|
||||
@@ -130,13 +148,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'CreateInstanceFormByGroup',
|
||||
components: { JsonEditor },
|
||||
components: {
|
||||
JsonEditor,
|
||||
CIReferenceAttr
|
||||
},
|
||||
props: {
|
||||
group: {
|
||||
type: Object,
|
||||
@@ -146,6 +167,10 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
ciTypeId: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
inject: ['getFieldType'],
|
||||
data() {
|
||||
@@ -183,6 +208,35 @@ export default {
|
||||
jsonEditorOk(jsonData) {
|
||||
this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) })
|
||||
},
|
||||
|
||||
getChoiceDefault(attr) {
|
||||
if (!attr?.default?.default) {
|
||||
return attr.is_list ? [] : null
|
||||
}
|
||||
|
||||
if (attr.is_list) {
|
||||
let defaultValue = []
|
||||
if (Array.isArray(attr.default.default)) {
|
||||
defaultValue = attr.default.default
|
||||
} else {
|
||||
defaultValue = String(attr.default.default).split(',')
|
||||
}
|
||||
if (['0', '1', '11'].includes(attr.value_type)) {
|
||||
defaultValue = defaultValue?.map((item) => {
|
||||
const numberValue = Number(item)
|
||||
return Number.isNaN(numberValue) ? item : numberValue
|
||||
})
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
let defaultValue = attr.default.default
|
||||
if (['0', '1', '11'].includes(attr.value_type)) {
|
||||
const numberValue = Number(defaultValue)
|
||||
defaultValue = Number.isNaN(numberValue) ? attr.default.default : numberValue
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -92,6 +92,10 @@ export default {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-left: solid 1px @border-color-base;
|
||||
}
|
||||
@@ -99,6 +103,10 @@ export default {
|
||||
&_active {
|
||||
background-color: @primary-color;
|
||||
color: #FFFFFF;
|
||||
|
||||
&:hover {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,7 @@
|
||||
import { mapState } from 'vuex'
|
||||
import _ from 'lodash'
|
||||
import { valueTypeMap } from '@/modules/cmdb/utils/const'
|
||||
import { getPropertyType } from '@/modules/cmdb/utils/helper'
|
||||
|
||||
export default {
|
||||
name: 'AllAttrDrawer',
|
||||
@@ -48,7 +49,18 @@ export default {
|
||||
tableData: [],
|
||||
}
|
||||
},
|
||||
inject: ['providerGroupsData'],
|
||||
inject: {
|
||||
providerGroupsData: {
|
||||
default: () => {
|
||||
return () => {
|
||||
return {
|
||||
CITypeGroups: [],
|
||||
otherGroupAttributes: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
@@ -84,12 +96,7 @@ export default {
|
||||
})
|
||||
|
||||
otherAttrData.forEach((attr) => {
|
||||
if (attr.is_password) {
|
||||
attr.value_type = '7'
|
||||
}
|
||||
if (attr.is_link) {
|
||||
attr.value_type = '8'
|
||||
}
|
||||
attr.value_type = getPropertyType(attr)
|
||||
|
||||
attr.groupId = -1
|
||||
attr.groupName = this.$t('other')
|
||||
|
@@ -51,7 +51,7 @@
|
||||
@pushCITypeList="pushCITypeList"
|
||||
@addPlugin="openEditDrawer(null, 'add', 'plugin')"
|
||||
/>
|
||||
<EditDrawer ref="editDrawer" :is_inner="false" @updateNotInner="updateNotInner" />
|
||||
<EditDrawer ref="editDrawer" :isDiscoveryPage="false" @updateNotInner="updateNotInner" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@@ -21,9 +21,7 @@
|
||||
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
|
||||
{{ property.alias || property.name }}
|
||||
</div>
|
||||
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
|
||||
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
|
||||
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
|
||||
<div class="attribute-card_value-type">{{ valueTypeMap[getPropertyType(property)] }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="attribute-card-trigger"
|
||||
@@ -74,7 +72,10 @@
|
||||
!isUnique &&
|
||||
!['6'].includes(property.value_type) &&
|
||||
!property.is_password &&
|
||||
!property.is_list
|
||||
!property.is_list &&
|
||||
!property.is_reference &&
|
||||
!property.is_bool &&
|
||||
!(Array.isArray(property.choice_value) ? property.choice_value.length > 0 : false)
|
||||
"
|
||||
:title="$t(isShowId ? 'cmdb.ciType.cancelSetAsShow' : 'cmdb.ciType.setAsShow')"
|
||||
>
|
||||
@@ -101,6 +102,8 @@ import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon'
|
||||
import { valueTypeMap } from '../../utils/const'
|
||||
import TriggerForm from './triggerForm.vue'
|
||||
import { updateCIType } from '@/modules/cmdb/api/CIType'
|
||||
import { getPropertyType } from '../../utils/helper'
|
||||
|
||||
export default {
|
||||
name: 'AttributeCard',
|
||||
inject: {
|
||||
@@ -191,6 +194,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPropertyType,
|
||||
handleEdit() {
|
||||
this.$emit('edit')
|
||||
},
|
||||
|
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<a-form-item
|
||||
:label="$t('cmdb.ciType.referenceModel')"
|
||||
:extra="$t('cmdb.ciType.referenceModelTip1')"
|
||||
:label-col="formItemLayout.labelCol"
|
||||
:wrapper-col="formItemLayout.wrapperCol"
|
||||
>
|
||||
<a-select
|
||||
allowClear
|
||||
v-decorator="['reference_type_id', {
|
||||
rules: [{ required: true, message: $t('cmdb.ciType.referenceModelTip') }],
|
||||
initialValue: ''
|
||||
}]"
|
||||
showSearch
|
||||
optionFilterProp="title"
|
||||
@dropdownVisibleChange="handleDropdownVisibleChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item) in options"
|
||||
:key="item.value"
|
||||
:title="item.label"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'ReferenceModelSelect',
|
||||
props: {
|
||||
form: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isLazyRequire: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
formItemLayout: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isInit: false,
|
||||
options: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.isLazyRequire) {
|
||||
this.getSelectOptions()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleDropdownVisibleChange(open) {
|
||||
if (!this.isInit && open) {
|
||||
this.getSelectOptions()
|
||||
}
|
||||
},
|
||||
async getSelectOptions() {
|
||||
this.isInit = true
|
||||
const res = await getCITypes()
|
||||
|
||||
this.options = res.ci_types.map((ciType) => {
|
||||
return {
|
||||
value: ciType.id,
|
||||
label: ciType?.alias || ciType?.name || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -60,14 +60,20 @@
|
||||
v-decorator="['value_type', { rules: [{ required: true }] }]"
|
||||
@change="handleChangeValueType"
|
||||
>
|
||||
<a-select-option :value="key" :key="key" v-for="(value, key) in valueTypeMap">{{ value }}</a-select-option>
|
||||
<a-select-option :value="key" :key="key" v-for="(value, key) in valueTypeMap">
|
||||
<ops-icon :type="getPropertyIcon({ value_type: key })" />
|
||||
<span class="value-type-text">{{ value }}</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item></a-col
|
||||
>
|
||||
<a-col :span="currentValueType === '6' ? 24 : 12">
|
||||
<a-col
|
||||
v-if="currentValueType !== '11'"
|
||||
:span="currentValueType === '6' ? 24 : 12"
|
||||
>
|
||||
<a-form-item
|
||||
:label-col="{ span: currentValueType === '6' ? 4 : 8 }"
|
||||
:wrapper-col="{ span: currentValueType === '6' ? 18 : 12 }"
|
||||
:wrapper-col="{ span: currentValueType === '6' ? 18 : 15 }"
|
||||
:label="$t('cmdb.ciType.defaultValue')"
|
||||
>
|
||||
<template>
|
||||
@@ -77,6 +83,10 @@
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-input>
|
||||
<a-switch
|
||||
v-else-if="currentValueType === '10'"
|
||||
v-decorator="['default_value', { rules: [{ required: false }], valuePropName: 'checked' }]"
|
||||
/>
|
||||
<a-select
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
mode="tags"
|
||||
@@ -95,12 +105,7 @@
|
||||
</a-input-number>
|
||||
<a-input
|
||||
style="width: 100%"
|
||||
v-else-if="
|
||||
currentValueType === '2' ||
|
||||
currentValueType === '5' ||
|
||||
currentValueType === '7' ||
|
||||
currentValueType === '8'
|
||||
"
|
||||
v-else-if="['2', '5', '7', '8', '9'].includes(currentValueType)"
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-input>
|
||||
@@ -157,7 +162,18 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
|
||||
<a-col
|
||||
v-if="currentValueType === '11'"
|
||||
:span="12"
|
||||
>
|
||||
<ReferenceModelSelect
|
||||
:form="form"
|
||||
:isLazyRequire="false"
|
||||
:formItemLayout="formItemLayout"
|
||||
/>
|
||||
</a-col>
|
||||
|
||||
<!-- <a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
|
||||
<a-form-item
|
||||
:hidden="currentValueType === '2' ? false : true"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
@@ -171,7 +187,6 @@
|
||||
<a-icon
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -189,10 +204,10 @@
|
||||
v-decorator="['is_index', { rules: [], valuePropName: 'checked' }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-col> -->
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.unique')"
|
||||
>
|
||||
@@ -206,7 +221,7 @@
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item
|
||||
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 8 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('required')"
|
||||
>
|
||||
@@ -219,7 +234,7 @@
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? { span: 12 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
>
|
||||
<template slot="label">
|
||||
@@ -228,9 +243,8 @@
|
||||
>{{ $t('cmdb.ciType.defaultShow') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.defaultShowTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:2px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -250,7 +264,7 @@
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? horizontalFormItemLayout.labelCol : { span: 8 }"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.isSortable')"
|
||||
>
|
||||
@@ -263,9 +277,9 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-col :span="6" v-if="!['6', '7', '10'].includes(currentValueType)">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
>
|
||||
<template slot="label">
|
||||
@@ -274,9 +288,8 @@
|
||||
>{{ $t('cmdb.ciType.list') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.listTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -297,7 +310,7 @@
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-form-item
|
||||
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 12 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
>
|
||||
<template slot="label">
|
||||
@@ -306,9 +319,8 @@
|
||||
>{{ $t('cmdb.ciType.isDynamic') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.dynamicTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -328,17 +340,22 @@
|
||||
</a-col>
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
|
||||
<a-row>
|
||||
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
|
||||
<a-col :span="24">
|
||||
<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()" />
|
||||
<RegSelect
|
||||
:isShowErrorMsg="false"
|
||||
:limitedFormat="getLimitedFormat()"
|
||||
:disabled="['6', '10', '11'].includes(currentValueType)"
|
||||
v-model="re_check"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
|
||||
<FontArea ref="fontArea" />
|
||||
<FontArea ref="fontArea" :fontColorDisabled="['8', '11'].includes(currentValueType)" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)">
|
||||
<a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')">
|
||||
<PreValueArea
|
||||
v-if="drawerVisible"
|
||||
@@ -346,10 +363,13 @@
|
||||
ref="preValueArea"
|
||||
:disabled="isShowComputedArea"
|
||||
:CITypeId="CITypeId"
|
||||
:enumValueType="enumValueType"
|
||||
/>
|
||||
|
||||
<a-button type="primary" size="small" ghost @click="resetPreValue" >{{ $t('reset') }}</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)">
|
||||
<a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<template slot="label">
|
||||
<span
|
||||
@@ -357,9 +377,8 @@
|
||||
>{{ $t('cmdb.ciType.computedAttribute') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -415,14 +434,17 @@ import {
|
||||
calcComputedAttribute,
|
||||
} from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { valueTypeMap } from '../../utils/const'
|
||||
import { getPropertyType, getPropertyIcon } from '../../utils/helper'
|
||||
import ComputedArea from './computedArea.vue'
|
||||
import PreValueArea from './preValueArea.vue'
|
||||
import FontArea from './fontArea.vue'
|
||||
import RegSelect from '@/components/RegexSelect'
|
||||
import ReferenceModelSelect from './attributeEdit/referenceModelSelect.vue'
|
||||
import { ENUM_VALUE_TYPE } from './preValueAttr/constants.js'
|
||||
|
||||
export default {
|
||||
name: 'AttributeEditForm',
|
||||
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect },
|
||||
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect, ReferenceModelSelect },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
@@ -451,6 +473,7 @@ export default {
|
||||
|
||||
defaultForDatetime: '',
|
||||
re_check: {},
|
||||
enumValueType: ENUM_VALUE_TYPE.INPUT
|
||||
}
|
||||
},
|
||||
|
||||
@@ -467,7 +490,7 @@ export default {
|
||||
return formLayout === 'horizontal'
|
||||
? {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 12 },
|
||||
wrapperCol: { span: 15 },
|
||||
}
|
||||
: {}
|
||||
},
|
||||
@@ -484,6 +507,7 @@ export default {
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
getPropertyIcon,
|
||||
async handleCreate() {
|
||||
try {
|
||||
await canDefineComputed()
|
||||
@@ -516,9 +540,7 @@ export default {
|
||||
}
|
||||
}
|
||||
if (property === 'is_list') {
|
||||
this.form.setFieldsValue({
|
||||
default_value: checked ? [] : '',
|
||||
})
|
||||
this.handleSwitchIsList(checked)
|
||||
}
|
||||
if (checked && property === 'is_sortable') {
|
||||
this.$message.warning(this.$t('cmdb.ciType.addAttributeTips1'))
|
||||
@@ -536,6 +558,26 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
handleSwitchIsList(checked) {
|
||||
let defaultValue = checked ? [] : ''
|
||||
|
||||
switch (this.currentValueType) {
|
||||
case '2':
|
||||
case '9':
|
||||
defaultValue = ''
|
||||
break
|
||||
case '10':
|
||||
defaultValue = checked ? '' : false
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
this.form.setFieldsValue({
|
||||
default_value: defaultValue,
|
||||
})
|
||||
},
|
||||
|
||||
async handleEdit(record, attributes) {
|
||||
try {
|
||||
await canDefineComputed()
|
||||
@@ -544,12 +586,7 @@ export default {
|
||||
this.canDefineComputed = false
|
||||
}
|
||||
const _record = _.cloneDeep(record)
|
||||
if (_record.is_password) {
|
||||
_record.value_type = '7'
|
||||
}
|
||||
if (_record.is_link) {
|
||||
_record.value_type = '8'
|
||||
}
|
||||
_record.value_type = getPropertyType(_record)
|
||||
this.drawerTitle = this.$t('cmdb.ciType.editAttribute')
|
||||
this.drawerVisible = true
|
||||
this.record = _record
|
||||
@@ -573,8 +610,13 @@ export default {
|
||||
is_dynamic: _record.is_dynamic,
|
||||
})
|
||||
}
|
||||
if (_record.value_type === '11') {
|
||||
this.form.setFieldsValue({
|
||||
reference_type_id: _record.reference_type_id
|
||||
})
|
||||
}
|
||||
console.log(_record)
|
||||
if (!['6'].includes(_record.value_type) && _record.re_check) {
|
||||
if (!['6', '10', '11'].includes(_record.value_type) && _record.re_check) {
|
||||
this.re_check = {
|
||||
value: _record.re_check,
|
||||
}
|
||||
@@ -583,7 +625,11 @@ export default {
|
||||
}
|
||||
if (_record.default) {
|
||||
this.$nextTick(() => {
|
||||
if (_record.value_type === '0') {
|
||||
if (_record.value_type === '10') {
|
||||
this.form.setFieldsValue({
|
||||
default_value: Boolean(_record.default.default),
|
||||
})
|
||||
} else if (_record.value_type === '0') {
|
||||
if (_record.is_list) {
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
@@ -639,7 +685,23 @@ export default {
|
||||
})
|
||||
}
|
||||
const _find = attributes.find((item) => item.id === _record.id)
|
||||
if (!['6', '7'].includes(_record.value_type)) {
|
||||
if (!['6', '7', '10', '11'].includes(_record.value_type)) {
|
||||
switch (_record.value_type) {
|
||||
case '0':
|
||||
case '1':
|
||||
this.enumValueType = ENUM_VALUE_TYPE.NUMBER
|
||||
break
|
||||
case '3':
|
||||
this.enumValueType = ENUM_VALUE_TYPE.DATE_TIME
|
||||
break
|
||||
case '4':
|
||||
this.enumValueType = ENUM_VALUE_TYPE.DATE
|
||||
break
|
||||
default:
|
||||
this.enumValueType = ENUM_VALUE_TYPE.INPUT
|
||||
break
|
||||
}
|
||||
|
||||
this.$refs.preValueArea.setData({
|
||||
choice_value: (_find || {}).choice_value || [],
|
||||
choice_web_hook: _record.choice_web_hook,
|
||||
@@ -669,10 +731,10 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
delete values['default_show']
|
||||
delete values['is_required']
|
||||
const { default_value } = values
|
||||
if (values.value_type === '0' && default_value) {
|
||||
if (values.value_type === '10') {
|
||||
values.default = { default: values.is_list ? default_value : Boolean(default_value) }
|
||||
} else if (values.value_type === '0' && default_value) {
|
||||
if (values.is_list) {
|
||||
values.default = { default: default_value || null }
|
||||
} else {
|
||||
@@ -700,29 +762,56 @@ export default {
|
||||
values.default = { default: null }
|
||||
}
|
||||
|
||||
delete values.default_value
|
||||
if (values.is_computed) {
|
||||
const computedAreaData = this.$refs.computedArea.getData()
|
||||
values = { ...values, ...computedAreaData }
|
||||
} else {
|
||||
// If it is a non-computed attribute, check to see if there is a predefined value
|
||||
if (!['6', '7'].includes(values.value_type)) {
|
||||
if (!['6', '7', '10', '11'].includes(values.value_type)) {
|
||||
const preValueAreaData = this.$refs.preValueArea.getData()
|
||||
// 预定义值校验错误
|
||||
if (preValueAreaData?.isError) {
|
||||
return
|
||||
}
|
||||
values = { ...values, ...preValueAreaData }
|
||||
}
|
||||
}
|
||||
|
||||
delete values['default_show']
|
||||
delete values['is_required']
|
||||
delete values.default_value
|
||||
|
||||
const fontOptions = this.$refs.fontArea.getData()
|
||||
if (values.value_type === '7') {
|
||||
values.value_type = '2'
|
||||
values.is_password = true
|
||||
}
|
||||
if (values.value_type === '8') {
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
}
|
||||
if (values.value_type !== '6') {
|
||||
|
||||
if (!['6', '10', '11'].includes(values.value_type)) {
|
||||
values.re_check = this.re_check?.value ?? null
|
||||
}
|
||||
|
||||
// 重置数据类型
|
||||
switch (values.value_type) {
|
||||
case '7':
|
||||
values.value_type = '2'
|
||||
values.is_password = true
|
||||
break
|
||||
case '8':
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
break
|
||||
case '9':
|
||||
values.value_type = '2'
|
||||
break
|
||||
case '10':
|
||||
values.value_type = '7'
|
||||
values.is_bool = true
|
||||
break
|
||||
case '11':
|
||||
values.value_type = '0'
|
||||
values.is_reference = true
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (values.id) {
|
||||
await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed)
|
||||
} else {
|
||||
@@ -764,10 +853,19 @@ export default {
|
||||
},
|
||||
changeDefaultForDatetime(value) {
|
||||
this.defaultForDatetime = value
|
||||
if (value === '$custom_time') {
|
||||
this.form.setFieldsValue({
|
||||
default_value: undefined,
|
||||
})
|
||||
switch (value) {
|
||||
case '$custom_time':
|
||||
this.form.setFieldsValue({
|
||||
default_value: undefined,
|
||||
})
|
||||
break
|
||||
case '$updated_at':
|
||||
this.form.setFieldsValue({
|
||||
is_dynamic: true,
|
||||
})
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
onClick({ key }) {
|
||||
@@ -795,6 +893,12 @@ export default {
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
resetPreValue() {
|
||||
if (this.$refs.preValueArea) {
|
||||
this.$refs.preValueArea.resetData()
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
@@ -806,6 +910,9 @@ export default {
|
||||
line-height: 22px;
|
||||
color: #a5a9bc;
|
||||
}
|
||||
.value-type-text {
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.attribute-edit-form {
|
||||
|
@@ -16,21 +16,21 @@
|
||||
<a-space style="margin-bottom: 10px">
|
||||
<a-button @click="handleAddGroup" size="small" icon="plus">{{ $t('cmdb.ciType.group') }}</a-button>
|
||||
<a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button>
|
||||
<div>
|
||||
<div class="ci-types-attributes-flex">
|
||||
<a-tooltip
|
||||
v-for="typeKey in Object.keys(valueTypeMap)"
|
||||
:key="typeKey"
|
||||
:title="$t('cmdb.ciType.filterTips', { name: valueTypeMap[typeKey] })"
|
||||
v-for="item in valueTypeMap"
|
||||
:key="item.key"
|
||||
:title="$t('cmdb.ciType.filterTips', { name: item.value })"
|
||||
>
|
||||
<span
|
||||
@click="handleFilterType(typeKey)"
|
||||
@click="handleFilterType(item.key)"
|
||||
:class="{
|
||||
'ci-types-attributes-filter': true,
|
||||
'ci-types-attributes-filter-selected': attrTypeFilter.includes(typeKey),
|
||||
'ci-types-attributes-filter-selected': attrTypeFilter.includes(item.key),
|
||||
}"
|
||||
>
|
||||
<ops-icon :type="getPropertyIcon({ value_type: typeKey })" />
|
||||
{{ valueTypeMap[typeKey] }}
|
||||
<ops-icon :type="getPropertyIcon({ value_type: item.key })" />
|
||||
{{ item.value }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
@@ -200,7 +200,7 @@ import AttributeEditForm from './attributeEditForm.vue'
|
||||
import NewCiTypeAttrModal from './newCiTypeAttrModal.vue'
|
||||
import UniqueConstraint from './uniqueConstraint.vue'
|
||||
import { valueTypeMap } from '../../utils/const'
|
||||
import { getPropertyIcon } from '../../utils/helper'
|
||||
import { getPropertyIcon, getPropertyType } from '../../utils/helper'
|
||||
|
||||
export default {
|
||||
name: 'AttributesTable',
|
||||
@@ -233,6 +233,7 @@ export default {
|
||||
attrTypeFilter: [],
|
||||
unique: '',
|
||||
show_id: null,
|
||||
groupMaxCount: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -243,7 +244,12 @@ export default {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
valueTypeMap() {
|
||||
return valueTypeMap()
|
||||
const map = valueTypeMap()
|
||||
const keys = ['0', '1', '2', '9', '3', '4', '5', '6', '7', '8', '10', '11']
|
||||
return keys.map((key) => ({
|
||||
key,
|
||||
value: map[key]
|
||||
}))
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
@@ -335,6 +341,7 @@ export default {
|
||||
})
|
||||
this.CITypeGroups = values[1]
|
||||
this.CITypeGroups.forEach((g) => {
|
||||
this.groupMaxCount[g.name] = g.attributes.filter(a => a.inherited).length
|
||||
g.attributes.forEach((a) => {
|
||||
a.is_required = (temp[a.id] && temp[a.id].is_required) || false
|
||||
a.default_show = (temp[a.id] && temp[a.id].default_show) || false
|
||||
@@ -470,44 +477,43 @@ export default {
|
||||
handleChange(e, group) {
|
||||
console.log('changess', group)
|
||||
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
|
||||
if (group === -1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeSortedTips'))
|
||||
if (group === -1 || group === null) {
|
||||
this.refreshPage(this.$t('cmdb.ciType.attributeSortedTips'))
|
||||
} else if (e.moved.newIndex < this.groupMaxCount[group]) {
|
||||
this.refreshPage(this.$t('cmdb.ciType.attributeSortedTips2'))
|
||||
} else {
|
||||
transferCITypeAttrIndex(this.CITypeId, {
|
||||
from: { attr_id: e.moved.element.id, group_name: group },
|
||||
to: { order: e.moved.newIndex, group_name: group },
|
||||
to: { order: e.moved.newIndex, group_name: group }
|
||||
})
|
||||
.then((res) => this.$message.success(this.$t('updateSuccess')))
|
||||
.catch(() => {
|
||||
this.abortDraggable()
|
||||
})
|
||||
.then(() => this.$message.success(this.$t('updateSuccess')))
|
||||
.catch(() => this.init())
|
||||
}
|
||||
}
|
||||
|
||||
if (e.hasOwnProperty('added')) {
|
||||
this.addRemoveGroupFlag = { to: { group_name: group, order: e.added.newIndex }, inited: true }
|
||||
}
|
||||
|
||||
if (e.hasOwnProperty('removed')) {
|
||||
this.$nextTick(() => {
|
||||
transferCITypeAttrIndex(this.CITypeId, {
|
||||
from: { attr_id: e.removed.element.id, group_name: group },
|
||||
to: { group_name: this.addRemoveGroupFlag.to.group_name, order: this.addRemoveGroupFlag.to.order },
|
||||
})
|
||||
.then((res) => this.$message.success(this.$t('saveSuccess')))
|
||||
.catch(() => {
|
||||
this.abortDraggable()
|
||||
})
|
||||
.finally(() => {
|
||||
this.addRemoveGroupFlag = {}
|
||||
if (this.addRemoveGroupFlag.to.order < this.groupMaxCount[this.addRemoveGroupFlag.to.group_name]) {
|
||||
this.refreshPage(this.$t('cmdb.ciType.attributeSortedTips2'))
|
||||
} else {
|
||||
transferCITypeAttrIndex(this.CITypeId, {
|
||||
from: { attr_id: e.removed.element.id, group_name: group },
|
||||
to: { group_name: this.addRemoveGroupFlag.to.group_name, order: this.addRemoveGroupFlag.to.order }
|
||||
})
|
||||
.then(() => this.$message.success(this.$t('saveSuccess')))
|
||||
.catch(() => this.init())
|
||||
.finally(() => {
|
||||
this.addRemoveGroupFlag = {}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
abortDraggable() {
|
||||
this.$nextTick(() => {
|
||||
this.$router.push({ name: 'ci_type' })
|
||||
})
|
||||
refreshPage(errorMessage) {
|
||||
this.$message.error(errorMessage)
|
||||
this.init()
|
||||
},
|
||||
updatePropertyIndex() {
|
||||
const attributes = [] // All attributes
|
||||
@@ -585,26 +591,11 @@ export default {
|
||||
if (!attrTypeFilter.length) {
|
||||
return true
|
||||
} else {
|
||||
if (attrTypeFilter.includes('7') && attr.is_password) {
|
||||
return true
|
||||
}
|
||||
if (attrTypeFilter.includes('8') && attr.is_link) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
attrTypeFilter.includes(attr.value_type) &&
|
||||
attr.value_type === '2' &&
|
||||
(attr.is_password || attr.is_link)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (attrTypeFilter.includes(attr.value_type)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
const valueType = getPropertyType(attr)
|
||||
return attrTypeFilter.includes(valueType)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -618,6 +609,12 @@ export default {
|
||||
.ci-types-attributes {
|
||||
padding: 0 20px;
|
||||
overflow-y: auto;
|
||||
|
||||
&-flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ci-types-attributes-filter {
|
||||
color: @text-color_4;
|
||||
cursor: pointer;
|
||||
|
@@ -46,16 +46,21 @@
|
||||
v-decorator="['value_type', { rules: [{ required: true }], initialValue: '2' }]"
|
||||
@change="handleChangeValueType"
|
||||
>
|
||||
<a-select-option :value="key" :key="key" v-for="(value, key) in valueTypeMap">
|
||||
{{ value }}
|
||||
<span class="value-type-des" v-if="key === '3'">yyyy-mm-dd HH:MM:SS</span>
|
||||
<span class="value-type-des" v-if="key === '4'">yyyy-mm-dd</span>
|
||||
<span class="value-type-des" v-if="key === '5'">HH:MM:SS</span>
|
||||
<a-select-option :value="item.key" :key="item.key" v-for="(item) in valueTypeMap">
|
||||
<ops-icon :type="getPropertyIcon({ value_type: item.key })" />
|
||||
<span class="value-type-text">{{ item.value }}</span>
|
||||
<span class="value-type-des" v-if="item.key === '2'">{{ $t('cmdb.ciType.shortTextTip') }}</span>
|
||||
<span class="value-type-des" v-if="item.key === '3'">yyyy-mm-dd HH:MM:SS</span>
|
||||
<span class="value-type-des" v-if="item.key === '4'">yyyy-mm-dd</span>
|
||||
<span class="value-type-des" v-if="item.key === '5'">HH:MM:SS</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="currentValueType === '6' ? 24 : 12">
|
||||
<a-col
|
||||
v-if="currentValueType !== '11'"
|
||||
:span="currentValueType === '6' ? 24 : 12"
|
||||
>
|
||||
<a-form-item
|
||||
:label-col="{ span: currentValueType === '6' ? 4 : 8 }"
|
||||
:wrapper-col="{ span: currentValueType === '6' ? 18 : 15 }"
|
||||
@@ -68,6 +73,10 @@
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-input>
|
||||
<a-switch
|
||||
v-else-if="currentValueType === '10'"
|
||||
v-decorator="['default_value', { rules: [{ required: false }], valuePropName: 'checked' }]"
|
||||
/>
|
||||
<a-input-number
|
||||
style="width: 100%"
|
||||
v-else-if="currentValueType === '1'"
|
||||
@@ -86,12 +95,7 @@
|
||||
</a-select>
|
||||
<a-input
|
||||
style="width: 100%"
|
||||
v-else-if="
|
||||
currentValueType === '2' ||
|
||||
currentValueType === '5' ||
|
||||
currentValueType === '7' ||
|
||||
currentValueType === '8'
|
||||
"
|
||||
v-else-if="['2', '5', '7', '8', '9'].includes(currentValueType)"
|
||||
v-decorator="['default_value', { rules: [{ required: false }] }]"
|
||||
>
|
||||
</a-input>
|
||||
@@ -148,9 +152,19 @@
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col
|
||||
v-if="currentValueType === '11'"
|
||||
:span="12"
|
||||
>
|
||||
<ReferenceModelSelect
|
||||
:form="form"
|
||||
:formItemLayout="formItemLayout"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
|
||||
<!-- <a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
|
||||
<a-form-item
|
||||
:hidden="currentValueType === '2' ? false : true"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
@@ -164,7 +178,6 @@
|
||||
<a-icon
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -182,10 +195,10 @@
|
||||
v-decorator="['is_index', { rules: [], valuePropName: 'checked', initialValue: true }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-col> -->
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.unique')"
|
||||
>
|
||||
@@ -199,7 +212,7 @@
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item
|
||||
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 8 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('required')"
|
||||
>
|
||||
@@ -212,7 +225,7 @@
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? { span: 12 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
>
|
||||
<template slot="label">
|
||||
@@ -221,9 +234,8 @@
|
||||
>{{ $t('cmdb.ciType.defaultShow') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.defaultShowTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:2px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -243,7 +255,7 @@
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? horizontalFormItemLayout.labelCol : { span: 8 }"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.isSortable')"
|
||||
>
|
||||
@@ -256,20 +268,19 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<a-col :span="6" v-if="!['6', '7', '10'].includes(currentValueType)">
|
||||
<a-form-item
|
||||
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
>
|
||||
<template slot="label">
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.list') }}
|
||||
>
|
||||
<a-tooltip :title="$t('cmdb.ciType.listTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -278,6 +289,7 @@
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
{{ $t('cmdb.ciType.list') }}
|
||||
</span>
|
||||
</template>
|
||||
<a-switch
|
||||
@@ -290,18 +302,17 @@
|
||||
</a-col>
|
||||
<a-col span="6">
|
||||
<a-form-item
|
||||
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 12 } : horizontalFormItemLayout.labelCol"
|
||||
:label-col="horizontalFormItemLayout.labelCol"
|
||||
:wrapper-col="horizontalFormItemLayout.wrapperCol"
|
||||
>
|
||||
<template slot="label">
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.isDynamic') }}
|
||||
>
|
||||
<a-tooltip :title="$t('cmdb.ciType.dynamicTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -310,6 +321,7 @@
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
{{ $t('cmdb.ciType.isDynamic') }}
|
||||
</span>
|
||||
</template>
|
||||
<a-switch
|
||||
@@ -321,37 +333,44 @@
|
||||
</a-col>
|
||||
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
|
||||
<a-row>
|
||||
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
|
||||
<a-col :span="24">
|
||||
<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()" />
|
||||
<RegSelect
|
||||
v-model="re_check"
|
||||
:isShowErrorMsg="false"
|
||||
:limitedFormat="getLimitedFormat()"
|
||||
:disabled="['6', '10', '11'].includes(currentValueType)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
|
||||
<FontArea ref="fontArea" />
|
||||
<FontArea ref="fontArea" :fontColorDisabled="['8', '11'].includes(currentValueType)" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)">
|
||||
<a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')">
|
||||
<PreValueArea
|
||||
ref="preValueArea"
|
||||
:canDefineScript="canDefineScript"
|
||||
:disabled="isShowComputedArea"
|
||||
:CITypeId="CITypeId"
|
||||
:enumValueType="enumValueType"
|
||||
/>
|
||||
|
||||
<a-button type="primary" size="small" ghost @click="resetPreValue" >{{ $t('reset') }}</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)">
|
||||
<a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
|
||||
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<template slot="label">
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.computedAttribute') }}
|
||||
>
|
||||
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
|
||||
type="info-circle"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -360,6 +379,7 @@
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
{{ $t('cmdb.ciType.computedAttribute') }}
|
||||
</span>
|
||||
</template>
|
||||
<a-switch
|
||||
@@ -392,6 +412,9 @@ import ComputedArea from './computedArea.vue'
|
||||
import PreValueArea from './preValueArea.vue'
|
||||
import FontArea from './fontArea.vue'
|
||||
import RegSelect from '@/components/RegexSelect'
|
||||
import { getPropertyIcon } from '../../utils/helper'
|
||||
import ReferenceModelSelect from './attributeEdit/referenceModelSelect.vue'
|
||||
import { ENUM_VALUE_TYPE } from './preValueAttr/constants.js'
|
||||
|
||||
export default {
|
||||
name: 'CreateNewAttribute',
|
||||
@@ -401,6 +424,7 @@ export default {
|
||||
vueJsonEditor,
|
||||
FontArea,
|
||||
RegSelect,
|
||||
ReferenceModelSelect,
|
||||
},
|
||||
props: {
|
||||
hasFooter: {
|
||||
@@ -433,17 +457,24 @@ export default {
|
||||
defaultForDatetime: '',
|
||||
|
||||
re_check: {},
|
||||
enumValueType: ENUM_VALUE_TYPE.INPUT
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valueTypeMap() {
|
||||
return valueTypeMap()
|
||||
const map = valueTypeMap()
|
||||
const keys = ['0', '1', '2', '9', '3', '4', '5', '6', '7', '8', '10', '11']
|
||||
return keys.map((key) => ({
|
||||
key,
|
||||
value: map[key]
|
||||
}))
|
||||
},
|
||||
canDefineScript() {
|
||||
return this.canDefineComputed
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPropertyIcon,
|
||||
handleSubmit(isCloseModal = true) {
|
||||
this.form.validateFields(async (err, values) => {
|
||||
if (!err) {
|
||||
@@ -454,9 +485,10 @@ export default {
|
||||
console.log(values)
|
||||
const { is_required, default_show, default_value, is_dynamic } = values
|
||||
const data = { is_required, default_show, is_dynamic }
|
||||
delete values.is_required
|
||||
delete values.default_show
|
||||
if (values.value_type === '0' && default_value) {
|
||||
|
||||
if (values.value_type === '10') {
|
||||
values.default = { default: values.is_list ? (default_value || null) : Boolean(default_value) }
|
||||
} else if (values.value_type === '0' && default_value) {
|
||||
if (values.is_list) {
|
||||
values.default = { default: default_value || null }
|
||||
} else {
|
||||
@@ -483,45 +515,63 @@ export default {
|
||||
} else {
|
||||
values.default = { default: null }
|
||||
}
|
||||
delete values.default_value
|
||||
|
||||
if (values.is_computed) {
|
||||
const computedAreaData = this.$refs.computedArea.getData()
|
||||
values = { ...values, ...computedAreaData }
|
||||
} else {
|
||||
// If it is a non-computed attribute, check to see if there is a predefined value
|
||||
if (!['6', '7'].includes(values.value_type)) {
|
||||
if (!['6', '7', '10', '11'].includes(values.value_type)) {
|
||||
const preValueAreaData = this.$refs.preValueArea.getData()
|
||||
// 预定义值校验错误
|
||||
if (preValueAreaData?.isError) {
|
||||
return
|
||||
}
|
||||
values = { ...values, ...preValueAreaData }
|
||||
}
|
||||
}
|
||||
|
||||
delete values.is_required
|
||||
delete values.default_show
|
||||
delete values.default_value
|
||||
|
||||
const fontOptions = this.$refs.fontArea.getData()
|
||||
|
||||
// is_index: except for text, the index is hidden, text index default is true
|
||||
// 5 types in the box, is_index=true
|
||||
// json, password, link is_index=false
|
||||
if (['6', '7', '8'].includes(values.value_type)) {
|
||||
values.is_index = false
|
||||
} else if (values.value_type !== '2') {
|
||||
values.is_index = true
|
||||
}
|
||||
if (values.value_type === '7') {
|
||||
values.value_type = '2'
|
||||
values.is_password = true
|
||||
}
|
||||
if (values.value_type === '8') {
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
}
|
||||
if (values.value_type !== '6') {
|
||||
values.re_check = this.re_check?.value ?? null
|
||||
// 索引
|
||||
values.is_index = !['6', '7', '8', '9', '11'].includes(values.value_type)
|
||||
|
||||
// 重置数据类型
|
||||
switch (values.value_type) {
|
||||
case '7':
|
||||
values.value_type = '2'
|
||||
values.is_password = true
|
||||
break
|
||||
case '8':
|
||||
values.value_type = '2'
|
||||
values.is_link = true
|
||||
break
|
||||
case '9':
|
||||
values.value_type = '2'
|
||||
break
|
||||
case '10':
|
||||
values.value_type = '7'
|
||||
values.is_bool = true
|
||||
break
|
||||
case '11':
|
||||
values.value_type = '0'
|
||||
values.is_reference = true
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
const { attr_id } = await createAttribute({ ...values, option: { fontOptions } })
|
||||
|
||||
this.form.resetFields()
|
||||
this.currentValueType = '2'
|
||||
if (!['6'].includes(values.value_type) && !values.is_password) {
|
||||
if (this?.$refs?.preValueArea) {
|
||||
this.$refs.preValueArea.valueList = []
|
||||
}
|
||||
this.currentValueType = '2'
|
||||
this.$emit('done', attr_id, data, isCloseModal)
|
||||
} else {
|
||||
throw new Error()
|
||||
@@ -540,11 +590,34 @@ export default {
|
||||
}
|
||||
},
|
||||
handleChangeValueType(value) {
|
||||
this.currentValueType = value
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
default_value: this.form.getFieldValue('is_list') || value === '0' ? [] : null,
|
||||
})
|
||||
this.currentValueType = value
|
||||
if (['6', '10', '11'].includes(value)) {
|
||||
this.re_check = {}
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case '0':
|
||||
case '1':
|
||||
this.enumValueType = ENUM_VALUE_TYPE.NUMBER
|
||||
break
|
||||
case '3':
|
||||
this.enumValueType = ENUM_VALUE_TYPE.DATE_TIME
|
||||
break
|
||||
case '4':
|
||||
this.enumValueType = ENUM_VALUE_TYPE.DATE
|
||||
break
|
||||
default:
|
||||
this.enumValueType = ENUM_VALUE_TYPE.INPUT
|
||||
break
|
||||
}
|
||||
if (['0', '1', '3', '4'].includes(value)) {
|
||||
if (this.$refs.preValueArea) {
|
||||
this.$refs.preValueArea.initEnumValue()
|
||||
}
|
||||
}
|
||||
|
||||
this.handleSwitchType({ valueType: value })
|
||||
})
|
||||
},
|
||||
onChange(checked, property) {
|
||||
@@ -560,9 +633,7 @@ export default {
|
||||
}
|
||||
}
|
||||
if (property === 'is_list') {
|
||||
this.form.setFieldsValue({
|
||||
default_value: checked ? [] : '',
|
||||
})
|
||||
this.handleSwitchType({ checked })
|
||||
}
|
||||
if (checked && property === 'is_sortable') {
|
||||
this.$message.warning(this.$t('cmdb.ciType.addAttributeTips1'))
|
||||
@@ -579,6 +650,33 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleSwitchType({
|
||||
checked,
|
||||
valueType
|
||||
}) {
|
||||
checked = checked ?? this.form.getFieldValue('is_list')
|
||||
valueType = valueType ?? this.currentValueType
|
||||
|
||||
let defaultValue = checked || valueType === '0' ? [] : ''
|
||||
|
||||
switch (valueType) {
|
||||
case '2':
|
||||
case '9':
|
||||
defaultValue = ''
|
||||
break
|
||||
case '10':
|
||||
defaultValue = checked ? '' : false
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
this.form.setFieldsValue({
|
||||
default_value: defaultValue,
|
||||
})
|
||||
},
|
||||
|
||||
onJsonChange(value) {
|
||||
this.default_value_json_right = true
|
||||
},
|
||||
@@ -592,10 +690,19 @@ export default {
|
||||
},
|
||||
changeDefaultForDatetime(value) {
|
||||
this.defaultForDatetime = value
|
||||
if (value === '$custom_time') {
|
||||
this.form.setFieldsValue({
|
||||
default_value: undefined,
|
||||
})
|
||||
switch (value) {
|
||||
case '$custom_time':
|
||||
this.form.setFieldsValue({
|
||||
default_value: undefined,
|
||||
})
|
||||
break
|
||||
case '$updated_at':
|
||||
this.form.setFieldsValue({
|
||||
is_dynamic: true,
|
||||
})
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
onClick({ key }) {
|
||||
@@ -620,6 +727,12 @@ export default {
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
resetPreValue() {
|
||||
if (this.$refs.preValueArea) {
|
||||
this.$refs.preValueArea.resetData()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -629,6 +742,9 @@ export default {
|
||||
line-height: 22px;
|
||||
color: #a5a9bc;
|
||||
}
|
||||
.value-type-text {
|
||||
margin: 0 4px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.create-new-attribute {
|
||||
|
@@ -62,11 +62,8 @@ export default {
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
switch (this.activeKey) {
|
||||
case '6':
|
||||
this.$refs.triggerTable.getTableData()
|
||||
break
|
||||
case '5':
|
||||
this.$refs.reconciliationTable.getTableData()
|
||||
this.$refs.triggerTable.getTableData()
|
||||
break
|
||||
default:
|
||||
break
|
||||
@@ -87,11 +84,8 @@ export default {
|
||||
case '1':
|
||||
this.$refs.attributesTable.getCITypeGroupData()
|
||||
break
|
||||
case '6':
|
||||
this.$refs.triggerTable.getTableData()
|
||||
break
|
||||
case '5':
|
||||
this.$refs.reconciliationTable.getTableData()
|
||||
this.$refs.triggerTable.getTableData()
|
||||
break
|
||||
default:
|
||||
break
|
||||
@@ -113,4 +107,12 @@ export default {
|
||||
.grant-config-wrap {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ops-tab.ant-tabs {
|
||||
/deep/ .ant-tabs-bar {
|
||||
.ant-tabs-tab:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -21,7 +21,13 @@
|
||||
<a-icon type="underline" />
|
||||
</div>
|
||||
<div :style="{ width: '100px', marginLeft: '10px', display: 'inline-flex', alignItems: 'center' }">
|
||||
<a-icon type="font-colors" /><el-color-picker size="mini" v-model="fontOptions.color"> </el-color-picker>
|
||||
<a-icon type="font-colors" />
|
||||
<el-color-picker
|
||||
size="mini"
|
||||
:disabled="fontColorDisabled"
|
||||
v-model="fontOptions.color"
|
||||
>
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -30,6 +36,12 @@
|
||||
import _ from 'lodash'
|
||||
export default {
|
||||
name: 'FontArea',
|
||||
props: {
|
||||
fontColorDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fontOptions: {
|
||||
@@ -57,7 +69,11 @@ export default {
|
||||
if (flag) {
|
||||
return undefined
|
||||
} else {
|
||||
return this.fontOptions
|
||||
const fontOptions = _.cloneDeep(this.fontOptions)
|
||||
if (this.fontColorDisabled) {
|
||||
Reflect.deleteProperty(fontOptions, 'color')
|
||||
}
|
||||
return fontOptions
|
||||
}
|
||||
},
|
||||
setData({ fontOptions = {} }) {
|
||||
|
@@ -545,7 +545,7 @@ export default {
|
||||
},
|
||||
showIdSelectOptions() {
|
||||
const _showIdSelectOptions = this.currentTypeAttrs.filter(
|
||||
(item) => item.id !== this.unique_id && !['6'].includes(item.value_type) && !item.is_password && !item.is_list
|
||||
(item) => item.id !== this.unique_id && !['6'].includes(item.value_type) && !item.is_password && !item.is_list && !item.is_bool && !item.is_reference && !item?.choice_value?.length
|
||||
)
|
||||
if (this.showIdFilterInput) {
|
||||
return _showIdSelectOptions.filter(
|
||||
@@ -898,6 +898,7 @@ export default {
|
||||
this.loadCITypes()
|
||||
this.loading = false
|
||||
this.drawerVisible = false
|
||||
this.isInherit = false
|
||||
}, 1000)
|
||||
},
|
||||
async updateCIType(CITypeId, data) {
|
||||
@@ -916,6 +917,7 @@ export default {
|
||||
this.loadCITypes()
|
||||
this.loading = false
|
||||
this.drawerVisible = false
|
||||
this.isInherit = false
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
|
@@ -103,7 +103,7 @@ export default {
|
||||
await this.handleLinkAttrToCiType({ attr_id: this.targetKeys.map((i) => Number(i)) })
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
const { id, name, order, attributes } = this.currentGroup
|
||||
const { name, order, attributes } = this.currentGroup
|
||||
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||
this.targetKeys.forEach((key) => {
|
||||
attrIds.push(Number(key))
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
})
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
const { id, name, order, attributes } = this.currentGroup
|
||||
const { name, order, attributes } = this.currentGroup
|
||||
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||
attrIds.push(newAttrId)
|
||||
await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })
|
||||
|
@@ -7,33 +7,22 @@
|
||||
:tabBarStyle="{ borderBottom: 'none' }"
|
||||
>
|
||||
<a-tab-pane key="define" :disabled="disabled">
|
||||
<span style="font-size:12px;" slot="tab">{{ $t('define') }}</span>
|
||||
<PreValueTag type="add" :item="[]" @add="addNewValue" :disabled="disabled">
|
||||
<template #default>
|
||||
<a-button
|
||||
:style="{ marginBottom: '10px', fontSize: '12px', padding: '1px 7px' }"
|
||||
type="primary"
|
||||
ghost
|
||||
:disabled="disabled"
|
||||
size="small"
|
||||
>
|
||||
<a-icon type="plus" />{{ $t('add') }}</a-button
|
||||
>
|
||||
</template>
|
||||
</PreValueTag>
|
||||
<draggable :list="valueList" handle=".handle" :disabled="disabled">
|
||||
<PreValueTag
|
||||
:disabled="disabled"
|
||||
v-for="(item, index) in valueList"
|
||||
:key="`${item[0]}_${index}`"
|
||||
:item="item"
|
||||
@deleteValue="deleteValue"
|
||||
@editValue="editValue"
|
||||
/>
|
||||
</draggable>
|
||||
<span style="font-size:14px;" slot="tab">{{ $t('cmdb.ciType.enum') }}</span>
|
||||
<PreValueDefine
|
||||
v-model="valueList"
|
||||
:disabled="disabled"
|
||||
:enumValueType="enumValueType"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="builtin" :disabled="disabled">
|
||||
<div class="tab-builtin" slot="tab">
|
||||
<span class="tab-builtin-title">{{ $t('cmdb.ciType.builtin') }}</span>
|
||||
<span v-if="isOpenSource" class="tab-builtin-tag">Pro</span>
|
||||
</div>
|
||||
<PreValueBuiltIn ref="builtInRef" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="webhook" :disabled="disabled">
|
||||
<span style="font-size:12px;" slot="tab">Webhook</span>
|
||||
<span style="font-size:14px;" slot="tab">Webhook</span>
|
||||
<Webhook ref="webhook" style="margin-top:10px" />
|
||||
<a-form-model :model="form">
|
||||
<a-col :span="24">
|
||||
@@ -59,7 +48,7 @@
|
||||
</a-form-model>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="choice_other" :disabled="disabled">
|
||||
<span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.choiceOther') }}</span>
|
||||
<span style="font-size:14px;" slot="tab">{{ $t('cmdb.ciType.choiceOther') }}</span>
|
||||
<a-row :gutter="[24, 24]">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
@@ -179,11 +168,11 @@
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="script" :disabled="disabled || !canDefineScript">
|
||||
<span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span>
|
||||
<span style="font-size:14px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span>
|
||||
<a-form-item
|
||||
:style="{ marginBottom: '5px' }"
|
||||
:label="$t('cmdb.ciType.cascadeAttr')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-col="{ span: $i18n.locale === 'en' ? 3 : 2 }"
|
||||
:wrapper-col="{ span: 19 }"
|
||||
:extra="scriptCodeExtraText"
|
||||
labelAlign="left"
|
||||
@@ -191,7 +180,7 @@
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
placeholder="Please select"
|
||||
:placeholder="$t('placeholder2')"
|
||||
optionFilterProp="title"
|
||||
v-model="cascade_attributes"
|
||||
>
|
||||
@@ -211,9 +200,11 @@
|
||||
<div>2. {{ $t('cmdb.ciType.computedAttrTip2') }}</div>
|
||||
</div>
|
||||
|
||||
<a-button size="small" @click="showAllPropDrawer">
|
||||
{{ $t('cmdb.ciType.viewAllAttr') }}
|
||||
</a-button>
|
||||
<div class="all-attr-btn">
|
||||
<a-button size="small" @click="showAllPropDrawer">
|
||||
{{ $t('cmdb.ciType.viewAllAttr') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<AllAttrDrawer ref="allAttrDrawer" />
|
||||
|
||||
<CustomCodeMirror
|
||||
@@ -237,6 +228,9 @@ import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||
import { getCITypeCommonAttributesByTypeIds, getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import AttrFilter from './preValueAttr/attrFilter/index.vue'
|
||||
import AllAttrDrawer from './allAttrDrawer.vue'
|
||||
import PreValueDefine from './preValueAttr/define/index.vue'
|
||||
import PreValueBuiltIn from './preValueAttr/builtin/index.vue'
|
||||
import { ENUM_VALUE_TYPE } from './preValueAttr/constants.js'
|
||||
|
||||
import CustomCodeMirror from '@/components/CustomCodeMirror'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
@@ -245,7 +239,7 @@ require('codemirror/mode/python/python.js')
|
||||
|
||||
export default {
|
||||
name: 'PreValueArea',
|
||||
components: { draggable, PreValueTag, ColorPicker, Webhook, AttrFilter, CustomCodeMirror, AllAttrDrawer },
|
||||
components: { draggable, PreValueTag, ColorPicker, Webhook, AttrFilter, CustomCodeMirror, AllAttrDrawer, PreValueDefine, PreValueBuiltIn },
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
@@ -259,12 +253,26 @@ export default {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
enumValueType: {
|
||||
type: String,
|
||||
defualt: ENUM_VALUE_TYPE.INPUT
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defautValueColor,
|
||||
activeKey: 'define', // define webhook
|
||||
valueList: [],
|
||||
valueList: [
|
||||
[
|
||||
'',
|
||||
{
|
||||
style: {},
|
||||
icon: {},
|
||||
label: ''
|
||||
}
|
||||
]
|
||||
],
|
||||
form: {
|
||||
ret_key: '',
|
||||
},
|
||||
@@ -331,6 +339,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async getCITypeAttributesById() {
|
||||
if (!this.CITypeId) {
|
||||
this.curModelAttrList = []
|
||||
return
|
||||
}
|
||||
const res = await getCITypeAttributesById(this.CITypeId)
|
||||
let curModelAttrList = []
|
||||
if (res?.attributes?.length) {
|
||||
@@ -339,39 +351,25 @@ export default {
|
||||
this.curModelAttrList = curModelAttrList
|
||||
},
|
||||
|
||||
addNewValue(newValue, newStyle, newIcon) {
|
||||
if (newValue) {
|
||||
const idx = this.valueList.findIndex((v) => v[0] === newValue)
|
||||
if (idx > -1) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.valueExisted'))
|
||||
} else {
|
||||
this.valueList.push([newValue, { style: newStyle, icon: { ...newIcon } }])
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteValue(item) {
|
||||
const _valueList = _.cloneDeep(this.valueList)
|
||||
const idx = _valueList.findIndex((v) => v[0] === item[0])
|
||||
if (idx > -1) {
|
||||
_valueList.splice(idx, 1)
|
||||
this.valueList = _valueList
|
||||
}
|
||||
},
|
||||
editValue(item, newValue, newStyle, newIcon) {
|
||||
const _valueList = _.cloneDeep(this.valueList)
|
||||
const idx = _valueList.findIndex((v) => v[0] === item[0])
|
||||
if (idx > -1) {
|
||||
_valueList[idx] = [newValue, { style: newStyle, icon: { ...newIcon } }]
|
||||
this.valueList = _valueList
|
||||
}
|
||||
},
|
||||
getData() {
|
||||
if (this.activeKey === 'define') {
|
||||
if (this.activeKey === 'builtin') {
|
||||
return {
|
||||
choice_value: this.valueList,
|
||||
choice_value: [],
|
||||
choice_web_hook: null,
|
||||
choice_other: null,
|
||||
}
|
||||
} else if (this.activeKey === 'define') {
|
||||
if (this.validateDefine()) {
|
||||
return {
|
||||
isError: true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
choice_value: this.valueList.filter((item) => !['', null, undefined].includes(item?.[0])),
|
||||
choice_web_hook: null,
|
||||
choice_other: null
|
||||
}
|
||||
} else if (this.activeKey === 'webhook') {
|
||||
const choice_web_hook = this.$refs.webhook.getParams()
|
||||
choice_web_hook.ret_key = this.form.ret_key
|
||||
@@ -394,10 +392,24 @@ export default {
|
||||
return {
|
||||
choice_value: [],
|
||||
choice_web_hook: null,
|
||||
choice_other,
|
||||
choice_other
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
validateDefine() {
|
||||
const valueList = this.valueList.filter((item) => {
|
||||
return !['', null, undefined].includes(item?.[0])
|
||||
})
|
||||
const isRepeat = _.uniq(valueList.map(item => item?.[0])).length !== valueList.length
|
||||
if (isRepeat) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.enumValueTip2'))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
setData({ choice_value, choice_web_hook, choice_other }) {
|
||||
if (choice_web_hook) {
|
||||
this.activeKey = 'webhook'
|
||||
@@ -425,7 +437,27 @@ export default {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.valueList = choice_value
|
||||
let valueList = [[
|
||||
'',
|
||||
{
|
||||
style: {},
|
||||
icon: {},
|
||||
label: ''
|
||||
}
|
||||
]]
|
||||
if (choice_value?.length) {
|
||||
valueList = choice_value.map((item) => {
|
||||
return [
|
||||
item[0],
|
||||
{
|
||||
icon: item?.[1]?.['icon'] || {},
|
||||
style: item?.[1]?.['style'] || {},
|
||||
label: item?.[1]?.['label'] || item?.[0] || ''
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
this.valueList = valueList
|
||||
this.activeKey = 'define'
|
||||
}
|
||||
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
|
||||
@@ -436,6 +468,56 @@ export default {
|
||||
dom.style.backgroundColor = '#2f54eb'
|
||||
}
|
||||
},
|
||||
|
||||
resetData() {
|
||||
this.activeKey = 'define'
|
||||
this.$set(this, 'valueList', [
|
||||
[
|
||||
'',
|
||||
{
|
||||
style: {},
|
||||
icon: {},
|
||||
label: ''
|
||||
}
|
||||
]
|
||||
])
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.builtInRef) {
|
||||
this.$refs.builtInRef.setData({})
|
||||
}
|
||||
|
||||
if (this.$refs.webhook) {
|
||||
this.$refs.webhook.setParams({})
|
||||
}
|
||||
this.form.ret_key = ''
|
||||
|
||||
this.script = ''
|
||||
this.cascade_attributes = []
|
||||
if (this.$refs.codemirror) {
|
||||
this.$refs.codemirror.initCodeMirror('')
|
||||
}
|
||||
|
||||
this.choice_other = {
|
||||
type_ids: undefined,
|
||||
attr_id: undefined
|
||||
}
|
||||
if (this.$refs.attrFilter) {
|
||||
this.$refs.attrFilter.init(true, false)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
initEnumValue() {
|
||||
if (this.valueList) {
|
||||
const valueList = _.cloneDeep(this.valueList)
|
||||
valueList.forEach((item) => {
|
||||
item[0] = ''
|
||||
})
|
||||
this.$set(this, 'valueList', valueList)
|
||||
}
|
||||
},
|
||||
|
||||
setExpFromFilter(filterExp) {
|
||||
if (filterExp) {
|
||||
this.filterExp = `${filterExp}`
|
||||
@@ -462,6 +544,24 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tab-builtin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&-tag {
|
||||
background-color: #E1EFFF;
|
||||
color: #2F54EB;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
padding: 0 3px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.pre-value-edit-color {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -481,6 +581,12 @@ export default {
|
||||
line-height: 22px;
|
||||
color: #a5a9bc;
|
||||
}
|
||||
|
||||
.all-attr-btn {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
|
@@ -47,9 +47,32 @@
|
||||
>
|
||||
<ops-icon class="text-group-icon" type="veops-text" />
|
||||
</div>
|
||||
<CIReferenceAttr
|
||||
v-if="getAttr(rule.property).is_reference && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
class="select-filter"
|
||||
:referenceTypeId="getAttr(rule.property).reference_type_id"
|
||||
:value="rule.value"
|
||||
:disabled="disabled"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="getAttr(rule.property).is_bool && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
class="select-filter"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:value="rule.value"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
>
|
||||
<a-select-option key="1">
|
||||
true
|
||||
</a-select-option>
|
||||
<a-select-option key="0">
|
||||
false
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div
|
||||
class="input-group"
|
||||
v-if="isChoiceByProperty(rule.property) && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
v-else-if="isChoiceByProperty(rule.property) && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
@@ -64,9 +87,9 @@
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
id: String(node[0] || ''),
|
||||
label: node[1] ? node[1].label || node[0] : node[0],
|
||||
children: node.children && node.children.length ? node.children : undefined,
|
||||
}
|
||||
}
|
||||
"
|
||||
@@ -148,9 +171,13 @@
|
||||
|
||||
<script>
|
||||
import { compareTypeList } from '../constants.js'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ValueControls',
|
||||
components: {
|
||||
CIReferenceAttr
|
||||
},
|
||||
props: {
|
||||
rule: {
|
||||
type: Object,
|
||||
@@ -215,7 +242,10 @@ export default {
|
||||
...this.rule,
|
||||
[key]: value
|
||||
})
|
||||
}
|
||||
},
|
||||
getAttr(property) {
|
||||
return this.attrList.find((item) => item.name === property) || {}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -270,4 +300,21 @@ export default {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.select-filter {
|
||||
height: 36px;
|
||||
width: 136px;
|
||||
|
||||
/deep/ .ant-select-selection {
|
||||
height: 36px;
|
||||
background: #f7f8fa;
|
||||
line-height: 36px;
|
||||
border: none;
|
||||
|
||||
.ant-select-selection__rendered {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -0,0 +1,104 @@
|
||||
export const BUILT_IN_TYPE = {
|
||||
DEPARTMENT: 'department', // 部门
|
||||
USER: 'user', // 用户
|
||||
USER_GROUP: 'userGroup' // 用户组
|
||||
}
|
||||
|
||||
export const DISPLAY_VALUE_SELECT = [
|
||||
{ label: 'cs.companyStructure.nickname', value: 'nickname' },
|
||||
{ label: 'cs.companyStructure.email', value: 'email' },
|
||||
{ label: 'cs.companyStructure.mobile', value: 'mobile' },
|
||||
]
|
||||
|
||||
export const USER_FILTER_SELECT = [
|
||||
{ label: 'cs.companyStructure.nickname', value: 'nickname' },
|
||||
{ label: 'cs.companyStructure.username', value: 'username' },
|
||||
{ label: 'cs.companyStructure.email', value: 'email' },
|
||||
{
|
||||
label: 'cs.companyStructure.sex',
|
||||
value: 'sex',
|
||||
is_choice: true,
|
||||
choice_value: [
|
||||
{ label: 'cs.companyStructure.male', value: '男' },
|
||||
{ label: 'cs.companyStructure.female', value: '女' },
|
||||
],
|
||||
},
|
||||
{ label: 'cs.companyStructure.mobile', value: 'mobile' },
|
||||
{ label: 'cs.companyStructure.departmentName', value: 'department_name' },
|
||||
{ label: 'cs.companyStructure.positionName', value: 'position_name' },
|
||||
{ label: 'cs.companyStructure.supervisor', value: 'direct_supervisor_id' },
|
||||
{ label: 'cs.companyStructure.annualLeave', value: 'annual_leave' },
|
||||
{ label: 'cs.companyStructure.currentCompany', value: 'current_company' },
|
||||
{ label: 'cs.companyStructure.dfcEntryDate', value: 'dfc_entry_date' },
|
||||
{ label: 'cs.companyStructure.entryDate', value: 'entry_date' },
|
||||
{
|
||||
label: 'cs.companyStructure.isInternship',
|
||||
value: 'is_internship',
|
||||
is_choice: true,
|
||||
choice_value: [
|
||||
{ label: 'cs.companyStructure.fullTime', value: 0 },
|
||||
{ label: 'cs.companyStructure.internship', value: 1 },
|
||||
],
|
||||
},
|
||||
{ label: 'cs.companyStructure.leaveDate', value: 'leave_date' },
|
||||
{ label: 'cs.companyStructure.idCard', value: 'id_card' },
|
||||
{ label: 'cs.companyStructure.nation', value: 'nation' },
|
||||
{ label: 'cs.companyStructure.idPlace', value: 'id_place' },
|
||||
{
|
||||
label: 'cs.companyStructure.party',
|
||||
value: 'party',
|
||||
is_choice: true,
|
||||
choice_value: [
|
||||
{ label: 'cs.companyStructure.partyMember', value: '党员' },
|
||||
{ label: 'cs.companyStructure.member', value: '团员' },
|
||||
{ label: 'cs.companyStructure.masses', value: '群众' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'cs.companyStructure.householdRegistrationType',
|
||||
value: 'household_registration_type',
|
||||
is_choice: true,
|
||||
choice_value: [
|
||||
{ label: 'cs.companyStructure.town', value: '城镇' },
|
||||
{ label: 'cs.companyStructure.agriculture', value: '农业' },
|
||||
],
|
||||
},
|
||||
{ label: 'cs.companyStructure.hometown', value: 'hometown' },
|
||||
{
|
||||
label: 'cs.companyStructure.marry',
|
||||
value: 'marry',
|
||||
is_choice: true,
|
||||
choice_value: [
|
||||
{ label: 'cs.companyStructure.unmarried', value: '未婚' },
|
||||
{ label: 'cs.companyStructure.married', value: '已婚' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'cs.companyStructure.maxDegree',
|
||||
value: 'max_degree',
|
||||
is_choice: true,
|
||||
choice_value: [
|
||||
{ label: 'cs.companyStructure.phd', value: '博士' },
|
||||
{ label: 'cs.companyStructure.master', value: '硕士' },
|
||||
{
|
||||
label: 'cs.companyStructure.undergraduate',
|
||||
value: '本科',
|
||||
},
|
||||
{ label: 'cs.companyStructure.specialist', value: '专科' },
|
||||
{ label: 'cs.companyStructure.highSchool', value: '高中' },
|
||||
],
|
||||
},
|
||||
{ label: 'cs.companyStructure.emergencyPerson', value: 'emergency_person' },
|
||||
{ label: 'cs.companyStructure.emergencyPhone', value: 'emergency_phone' },
|
||||
{ label: 'cs.companyStructure.bankCardNumber', value: 'bank_card_number' },
|
||||
{ label: 'cs.companyStructure.bankCardName', value: 'bank_card_name' },
|
||||
{ label: 'cs.companyStructure.openingBank', value: 'opening_bank' },
|
||||
{ label: 'cs.companyStructure.accountOpeningLocation', value: 'account_opening_location' },
|
||||
{ label: 'cs.companyStructure.birthDate', value: 'birth_date' },
|
||||
{ label: 'cs.companyStructure.nationalityRegion', value: 'nationality_region' },
|
||||
{ label: 'cs.companyStructure.birthPlace', value: 'birth_place' },
|
||||
{ label: 'cs.companyStructure.firstEntryDate', value: 'first_entry_date' },
|
||||
{ label: 'cs.companyStructure.estimatedDepartureDate', value: 'estimated_departure_date' },
|
||||
// { label: '角色', value: 'roles' },
|
||||
{ label: 'cs.companyStructure.lastLogin', value: 'last_login' },
|
||||
]
|
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div class="builtin">
|
||||
<div class="builtin-tab">
|
||||
<div
|
||||
v-for="(item) in tabList"
|
||||
:key="item.key"
|
||||
:class="['builtin-tab-item', activeKey === item.key ? 'builtin-tab-item_active' : '']"
|
||||
@click="clickTab(item.key)"
|
||||
>
|
||||
<ops-icon :type="item.icon" class="builtin-tab-item-icon" />
|
||||
<span class="builtin-tab-item-title">{{ $t(item.title) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="activeKey === BUILT_IN_TYPE.DEPARTMENT"
|
||||
class="builtin-department"
|
||||
>
|
||||
<a-icon class="builtin-department-icon" type="info-circle" />
|
||||
<span class="builtin-department-tip">{{ $t('cmdb.ciType.departmentTip') }}</span>
|
||||
</div>
|
||||
|
||||
<a-form
|
||||
:form="form"
|
||||
v-show="activeKey === BUILT_IN_TYPE.USER"
|
||||
class="builtin-user"
|
||||
>
|
||||
<a-form-item
|
||||
:label="$t('cmdb.ciType.filterUsers')"
|
||||
:label-col="formLayout.labelCol"
|
||||
:wrapper-col="formLayout.wrapperCol"
|
||||
>
|
||||
<UserFilterComp
|
||||
ref="userFilterRef"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="$t('cmdb.ciType.departmentCascadeDisplay')"
|
||||
v-if="activeKey === BUILT_IN_TYPE.USER"
|
||||
:label-col="formLayout.labelCol"
|
||||
:wrapper-col="formLayout.wrapperCol"
|
||||
>
|
||||
<a-switch
|
||||
v-decorator="['cascade_display', { rules: [{ required: false }], valuePropName: 'checked', initialValue: false }]"
|
||||
></a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="activeKey === BUILT_IN_TYPE.USER"
|
||||
:label="$t('cmdb.ciType.displayValue')"
|
||||
:label-col="formLayout.labelCol"
|
||||
:wrapper-col="formLayout.wrapperCol"
|
||||
>
|
||||
<a-select
|
||||
class="builtin-select"
|
||||
v-decorator="['display_value', { rules: [{ required: true, message: $t('cmdb.ciType.displayValueSelectTip') }], initialValue: 'nickname' }]"
|
||||
showSearch
|
||||
optionFilterProp="title"
|
||||
:placeholder="$t('cmdb.ciType.displayValueSelectTip')"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item) in DISPLAY_VALUE_SELECT"
|
||||
:value="item.value"
|
||||
:key="item.value"
|
||||
:title="$t(item.label)"
|
||||
>
|
||||
{{ $t(item.label) }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-form
|
||||
:form="form"
|
||||
v-if="activeKey === BUILT_IN_TYPE.USER_GROUP"
|
||||
class="builtin-usergroup"
|
||||
>
|
||||
<a-form-item
|
||||
:label="$t('cmdb.ciType.userGroup')"
|
||||
:label-col="formLayout.labelCol"
|
||||
:wrapper-col="formLayout.wrapperCol"
|
||||
>
|
||||
<a-select
|
||||
class="builtin-select"
|
||||
v-decorator="['user_group_key', { rules: [{ required: true, message: $t('cmdb.ciType.userGroupSelectTip') }] }]"
|
||||
showSearch
|
||||
optionFilterProp="title"
|
||||
:placeholder="$t('cmdb.ciType.userGroupSelectTip')"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item) in userGroupList"
|
||||
:value="item.group_id"
|
||||
:key="item.group_id"
|
||||
:title="item.group_name"
|
||||
>
|
||||
{{ item.group_name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label-col="formLayout.labelCol"
|
||||
:wrapper-col="formLayout.wrapperCol"
|
||||
:label="$t('cmdb.ciType.displayValue')"
|
||||
>
|
||||
<a-select
|
||||
class="builtin-select"
|
||||
v-decorator="['display_value', { rules: [{ required: true, message: $t('cmdb.ciType.displayValueSelectTip') }], initialValue: 'nickname' }]"
|
||||
showSearch
|
||||
optionFilterProp="title"
|
||||
:placeholder="$t('cmdb.ciType.displayValueSelectTip')"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item) in DISPLAY_VALUE_SELECT"
|
||||
:value="item.value"
|
||||
:key="item.value"
|
||||
:title="$t(item.label)"
|
||||
>
|
||||
{{ $t(item.label) }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BUILT_IN_TYPE, DISPLAY_VALUE_SELECT } from './constants.js'
|
||||
import UserFilterComp from './userFilterComp/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'PreValueBuiltIn',
|
||||
components: { UserFilterComp },
|
||||
data() {
|
||||
return {
|
||||
BUILT_IN_TYPE,
|
||||
DISPLAY_VALUE_SELECT,
|
||||
activeKey: BUILT_IN_TYPE.DEPARTMENT,
|
||||
tabList: [
|
||||
{
|
||||
key: BUILT_IN_TYPE.DEPARTMENT,
|
||||
title: 'cmdb.ciType.department',
|
||||
icon: 'veops-department'
|
||||
},
|
||||
{
|
||||
key: BUILT_IN_TYPE.USER,
|
||||
title: 'cmdb.ciType.user',
|
||||
icon: 'icon-shidi-yonghu'
|
||||
},
|
||||
{
|
||||
key: BUILT_IN_TYPE.USER_GROUP,
|
||||
title: 'cmdb.ciType.userGroup',
|
||||
icon: 'ops-setting-group'
|
||||
}
|
||||
],
|
||||
userGroupList: [],
|
||||
allFlatEmployees: [],
|
||||
form: this.$form.createForm(this),
|
||||
formLayout: {
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 16 },
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setData(data) {
|
||||
this.activeKey = data?.builtin_type || BUILT_IN_TYPE.DEPARTMENT
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
cascade_display: data?.cascade_display ?? false,
|
||||
display_value: data?.display_value ?? undefined,
|
||||
user_group_key: data?.user_group_key ?? undefined
|
||||
})
|
||||
|
||||
this.$refs.userFilterRef.setRuleList(data?.filter_rule_list || [])
|
||||
})
|
||||
},
|
||||
|
||||
getData() {
|
||||
let params = {}
|
||||
this.form.validateFields({ force: true }, (err, values) => {
|
||||
if (err) {
|
||||
params.isError = true
|
||||
return
|
||||
}
|
||||
|
||||
params = {
|
||||
...values,
|
||||
builtin_type: this.activeKey,
|
||||
}
|
||||
|
||||
if (this.activeKey === BUILT_IN_TYPE.USER) {
|
||||
params.filter_rule_list = this.$refs.userFilterRef.getRuleList()
|
||||
}
|
||||
})
|
||||
|
||||
return params
|
||||
},
|
||||
|
||||
async clickTab(key) {
|
||||
this.activeKey = key
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.builtin {
|
||||
width: 100%;
|
||||
|
||||
&-tab {
|
||||
padding: 4px 80px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 60px;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border: solid 1px #E4E7ED;
|
||||
border-radius: 2px;
|
||||
padding: 0 20px;
|
||||
min-width: 72px;
|
||||
height: 64px;
|
||||
cursor: pointer;
|
||||
|
||||
&-icon {
|
||||
font-size: 20px;
|
||||
color: #A5A9BC;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 14px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
&_active {
|
||||
border-color: #B1C9FF;
|
||||
|
||||
.builtin-tab-item-icon {
|
||||
color: #7F97FA;
|
||||
}
|
||||
|
||||
.builtin-tab-item-title {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-department {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 60px;
|
||||
|
||||
&-icon {
|
||||
font-size: 12px;
|
||||
color: #A5A9BC;
|
||||
}
|
||||
|
||||
&-tip {
|
||||
color: #4E5969;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-select {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,16 @@
|
||||
export const ruleTypeList = [
|
||||
{ value: '&', label: 'cs.components.and' },
|
||||
{ value: '|', label: 'cs.components.or' },
|
||||
]
|
||||
|
||||
export const expList = [
|
||||
{ value: 1, label: 'cs.components.equal' },
|
||||
{ value: 2, label: 'cs.components.notEqual' },
|
||||
{ value: 7, label: 'cs.components.isEmpty' },
|
||||
{ value: 8, label: 'cs.components.isNotEmpty' },
|
||||
]
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: 5, label: 'cs.components.moreThan' },
|
||||
{ value: 6, label: 'cs.components.lessThan' },
|
||||
]
|
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<treeselect
|
||||
class="employee-tree-select"
|
||||
:style="{ width: '100px' }"
|
||||
:disable-branch-nodes="!multiple"
|
||||
:multiple="multiple"
|
||||
:options="employeeTreeSelectOption"
|
||||
:placeholder="readOnly ? '' : placeholder || $t('cs.components.selectEmployee')"
|
||||
v-model="treeValue"
|
||||
:max-height="150"
|
||||
:noChildrenText="$t('cs.components.empty')"
|
||||
:noOptionsText="$t('cs.components.empty')"
|
||||
:class="className ? className : 'ops-setting-treeselect'"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:limit="limit"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
v-bind="$attrs"
|
||||
:zIndex="1050"
|
||||
:flat="flat"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import { formatOption } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'EmployeeTreeSelect',
|
||||
components: {
|
||||
Treeselect,
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array, Number, null],
|
||||
default: null,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: 'ops-setting-treeselect',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
idType: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
departmentKey: {
|
||||
type: String,
|
||||
default: 'department_id',
|
||||
},
|
||||
employeeKey: {
|
||||
type: String,
|
||||
default: 'employee_id',
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
flat: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
inject: {
|
||||
provide_allTreeDepAndEmp: {
|
||||
from: 'provide_allTreeDepAndEmp',
|
||||
default: () => {
|
||||
return () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
readOnly: {
|
||||
from: 'readOnly',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
treeValue: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
allTreeDepAndEmp() {
|
||||
return this.provide_allTreeDepAndEmp()
|
||||
},
|
||||
employeeTreeSelectOption() {
|
||||
const option = formatOption(this.allTreeDepAndEmp, this.idType, false, this.departmentKey, this.employeeKey)
|
||||
return option
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.employee-tree-select {
|
||||
/deep/ .vue-treeselect__menu {
|
||||
width: 100px;
|
||||
overflow-x: auto;
|
||||
|
||||
& > .vue-treeselect__list {
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.vue-treeselect__label-container {
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.vue-treeselect__option {
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,333 @@
|
||||
<template>
|
||||
<div class="user-filter">
|
||||
<a-button
|
||||
v-if="!ruleList.length"
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
class="add-btn"
|
||||
@click="handleAddRule"
|
||||
>
|
||||
<a-icon type="plus" />
|
||||
{{ $t('add') }}
|
||||
</a-button>
|
||||
<template v-else>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '50px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '50px', '--custom-height': '36px', position: 'absolute', top: '-30px', left: 0 }"
|
||||
v-model="item.relation"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: $t(node.label),
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '140px', '--custom-height': '36px' }"
|
||||
v-model="item.column"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="userFilterSelect"
|
||||
:maxHeight="150"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: $t(node.label),
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<a-tooltip :title="$t(node.label)">
|
||||
{{ $t(node.label) }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<a-tooltip :title="$t(node.label)">
|
||||
{{ $t(node.label) }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '90px', '--custom-height': '36px' }"
|
||||
v-model="item.operator"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...expList, ...compareTypeList]"
|
||||
:maxHeight="150"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: $t(node.label),
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
>
|
||||
<div
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<a-tooltip :title="$t(node.label)">
|
||||
{{ $t(node.label) }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<a-tooltip :title="$t(node.label)">
|
||||
{{ $t(node.label) }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '36px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.column) && (item.operator === 1 || item.operator === 2)"
|
||||
:options="getChoiceValueByProperty(item.column)"
|
||||
:placeholder="$t('cs.components.selectPlaceholder')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: $t(node.label),
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="$t(node.label)"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ $t(node.label) }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<div v-else-if="item.column === 'direct_supervisor_id' && (item.operator === 1 || item.operator === 2)">
|
||||
<EmployeeTreeSelect v-model="item.value" className="custom-treeselect"/>
|
||||
</div>
|
||||
<a-input
|
||||
v-else-if="item.operator !== 7 && item.operator !== 8"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cs.components.operatorInPlaceholder') : ''"
|
||||
class="ops-input"
|
||||
></a-input>
|
||||
<a-tooltip :title="$t('cs.components.copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="veops-reduce"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('add')">
|
||||
<a class="operation" @click="handleAddRule"><ops-icon type="veops-increase"/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import { ruleTypeList, expList, compareTypeList } from './constants'
|
||||
import { USER_FILTER_SELECT } from '../constants'
|
||||
import EmployeeTreeSelect from './employeeTreeSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'UserFilterComp',
|
||||
components: {
|
||||
Treeselect,
|
||||
EmployeeTreeSelect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
USER_FILTER_SELECT,
|
||||
ruleTypeList,
|
||||
expList,
|
||||
compareTypeList,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
userFilterSelect: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async setRuleList(ruleList) {
|
||||
this.ruleList = ruleList?.length ? ruleList : []
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
relation: '&',
|
||||
column: this.userFilterSelect?.[0]?.value ?? null,
|
||||
operator: 1,
|
||||
value: null,
|
||||
})
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
|
||||
getRuleList() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
const ruleList = _.cloneDeep(this.ruleList)
|
||||
ruleList.forEach((item, index) => {
|
||||
if (item.column === 'direct_supervisor_id') {
|
||||
ruleList[index].value = item.value ? (item.value + '').includes('-') ? +item.value.split('-')[1] : +item.value : 0
|
||||
}
|
||||
})
|
||||
if (ruleList?.length > 0) {
|
||||
ruleList[0].relation = '&'
|
||||
}
|
||||
|
||||
return ruleList.map((rule) => {
|
||||
return _.omit(rule, 'id')
|
||||
})
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 7 || value === 8) {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
value: null,
|
||||
operator: value
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
operator: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[1].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
isChoiceByProperty(column) {
|
||||
const _find = this.userFilterSelect.find((item) => item.value === column)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
getChoiceValueByProperty(column) {
|
||||
const _find = this.userFilterSelect.find((item) => item.value === column)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-filter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 36px;
|
||||
|
||||
.ops-input {
|
||||
height: 36px;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-size: 12px;
|
||||
width: 80px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -39,3 +39,10 @@ export const compareTypeList = [
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
||||
|
||||
export const ENUM_VALUE_TYPE = {
|
||||
INPUT: 'input',
|
||||
DATE: 'date',
|
||||
DATE_TIME: 'dateTIme',
|
||||
NUMBER: 'number'
|
||||
}
|
||||
|
@@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
:visible="popoverVisible"
|
||||
placement="bottom"
|
||||
trigger="focus"
|
||||
overlayClassName="pre-value-edit-popover"
|
||||
@visibleChange="handleVisibleChange"
|
||||
>
|
||||
<div @click="popoverVisible = true" ref="popoverLabelRef">
|
||||
<a-input
|
||||
v-show="!labelData.label || popoverVisible"
|
||||
type="text"
|
||||
:style="{ width: '210px' }"
|
||||
:value="labelData.label"
|
||||
@change="changeLabel"
|
||||
>
|
||||
</a-input>
|
||||
|
||||
<div
|
||||
class="pre-value-tag"
|
||||
:style="labelData.style ? labelData.style : {}"
|
||||
v-show="!popoverVisible && labelData.label"
|
||||
>
|
||||
<span>
|
||||
<img
|
||||
v-if="labelData.icon.id && labelData.icon.url"
|
||||
:src="`/api/common-setting/v1/file/${labelData.icon.url}`"
|
||||
:style="{ maxHeight: '12px', maxWidth: '12px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="labelData.icon.name"
|
||||
:type="labelData.icon.name"
|
||||
:style="{ marginRight: '5px', color: labelData.icon.color || '#595959' }"
|
||||
/>
|
||||
<a-tooltip :title="labelData.label">
|
||||
<span class="pre-value-tag-text">{{ labelData.label }}</span>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="preValueEdit" slot="content">
|
||||
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('icon') }}</a-divider>
|
||||
<CustomIconSelect
|
||||
:style="{ marginLeft: '10px' }"
|
||||
:value="labelData.icon"
|
||||
@change="changeIcon"
|
||||
/>
|
||||
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('cmdb.ciType.font') }}</a-divider>
|
||||
<div :style="{ display: 'flex', justifyContent: 'space-around' }">
|
||||
<div
|
||||
@click="changeFontStyle('fontWeight', 'bold')"
|
||||
:class="`attributes-font-icon ${labelData.style.fontWeight === 'bold' ? 'attributes-font-icon-selected' : ''}`"
|
||||
>
|
||||
<a-icon type="bold" />
|
||||
</div>
|
||||
<div
|
||||
@click="changeFontStyle('fontStyle', 'italic')"
|
||||
:class="`attributes-font-icon ${labelData.style.fontStyle === 'italic' ? 'attributes-font-icon-selected' : ''}`"
|
||||
>
|
||||
<a-icon type="italic" />
|
||||
</div>
|
||||
<div
|
||||
@click="changeFontStyle('textDecoration', 'underline')"
|
||||
:class="
|
||||
`attributes-font-icon ${labelData.style.textDecoration === 'underline' ? 'attributes-font-icon-selected' : ''}`
|
||||
"
|
||||
>
|
||||
<a-icon type="underline" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('cmdb.ciType.color') }}</a-divider>
|
||||
<div :style="{ display: 'flex', justifyContent: 'space-around' }">
|
||||
<div class="attributes-font-color">
|
||||
<a-icon type="font-colors" />
|
||||
<el-color-picker
|
||||
size="mini"
|
||||
:value="labelData.style.color"
|
||||
@change="(v) => changeFontStyle('color', v)"
|
||||
:predefine="defaultBGColors"
|
||||
>
|
||||
</el-color-picker>
|
||||
</div>
|
||||
<div class="attributes-font-color">
|
||||
<a-icon type="bg-colors" />
|
||||
<el-color-picker
|
||||
size="mini"
|
||||
:value="labelData.style.backgroundColor"
|
||||
@change="(v) => changeFontStyle('backgroundColor', v)"
|
||||
:predefine="defaultBGColors"
|
||||
>
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('operation') }}</a-divider>
|
||||
<div style="text-align:right;">
|
||||
<a-tooltip
|
||||
:title="$t('delete')"
|
||||
>
|
||||
<a>
|
||||
<a-icon
|
||||
@click="handleDelete"
|
||||
style="margin-right:10px;color:red;"
|
||||
type="delete"
|
||||
/>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
:title="$t('confirm')"
|
||||
>
|
||||
<a>
|
||||
<a-icon @click="popoverVisible = false" style="margin-right:10px;color:green;" type="check"/>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ColorPicker } from 'element-ui'
|
||||
import CustomIconSelect from '@/components/CustomIconSelect'
|
||||
import { defautValueColor, defaultBGColors } from '@/modules/cmdb/utils/const.js'
|
||||
import lang from 'element-ui/lib/locale/lang/en'
|
||||
import locale from 'element-ui/lib/locale'
|
||||
locale.use(lang)
|
||||
|
||||
export default {
|
||||
name: 'DefineLabel',
|
||||
components: { ElColorPicker: ColorPicker, CustomIconSelect },
|
||||
props: {
|
||||
labelData: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defautValueColor,
|
||||
defaultBGColors,
|
||||
popoverVisible: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.eventListener)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.eventListener)
|
||||
},
|
||||
methods: {
|
||||
eventListener(e) {
|
||||
if (this.popoverVisible) {
|
||||
const dom = this.$refs.preValueEdit
|
||||
const dom_label = this.$refs.popoverLabelRef
|
||||
const dom_icon = document.getElementById(`custom-icon-select-popover`)
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (dom) {
|
||||
const isSelf =
|
||||
dom.contains(e.target) || (dom_label && dom_label.contains(e.target)) || (dom_icon && dom_icon.contains(e.target))
|
||||
if (!isSelf) {
|
||||
this.popoverVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleDelete() {
|
||||
this.popoverVisible = false
|
||||
this.$emit('deleteData')
|
||||
},
|
||||
|
||||
changeFontStyle(key, value) {
|
||||
const style = {
|
||||
...(this.labelData.style || {}),
|
||||
[key]: this.labelData.style[key] === value ? 'initial' : value,
|
||||
}
|
||||
this.$emit('change', 'style', style)
|
||||
},
|
||||
|
||||
changeLabel(e) {
|
||||
const value = e.target.value
|
||||
this.$emit('change', 'label', value)
|
||||
},
|
||||
|
||||
changeIcon(value) {
|
||||
this.$emit('change', 'icon', value)
|
||||
},
|
||||
|
||||
handleVisibleChange(v) {
|
||||
if (!v) {
|
||||
this.popoverVisible = false
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.pre-value-edit-color {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
.pre-value-edit-color-item {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
.pre-value-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
max-width: 100%;
|
||||
|
||||
&-text {
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover .pre-value-tag-dropdown-icon {
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
.pre-value-tag-dropdown {
|
||||
font-size: 10px;
|
||||
color: #999999;
|
||||
&:hover {
|
||||
color: #2f54eb;
|
||||
}
|
||||
.pre-value-tag-dropdown-icon {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.pre-value-tag-input {
|
||||
border: none;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
font-size: 12px;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.pre-value-edit-popover.ant-popover-placement-top .ant-popover-content {
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
.pre-value-edit-popover.ant-popover-placement-bottom .ant-popover-content {
|
||||
margin-top: -10px;
|
||||
}
|
||||
.pre-value-edit-popover {
|
||||
.ant-popover-content {
|
||||
width: 150px;
|
||||
.ant-popover-arrow {
|
||||
display: none;
|
||||
}
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
}
|
||||
}
|
||||
.attributes-font-icon {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
border: 1px solid #fff;
|
||||
&:hover {
|
||||
background-color: #eeeeee;
|
||||
border-color: #606266;
|
||||
}
|
||||
> i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
.attributes-font-icon-selected {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.attributes-font-color {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 50%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div class="define-wrap">
|
||||
<a-button
|
||||
v-if="!defineList.length"
|
||||
type="primary"
|
||||
ghost
|
||||
:disabled="disabled"
|
||||
size="small"
|
||||
class="add-btn"
|
||||
@click="addData"
|
||||
>
|
||||
<a-icon type="plus" />
|
||||
{{ $t('add') }}
|
||||
</a-button>
|
||||
|
||||
<vxe-table
|
||||
v-else
|
||||
ref="xTable"
|
||||
:data="defineList"
|
||||
size="mini"
|
||||
show-header-overflow
|
||||
:row-config="{ height: 46 }"
|
||||
:min-height="75"
|
||||
border="outer"
|
||||
class="define-wrap-table"
|
||||
>
|
||||
<vxe-column field="value" width="230" :title="$t('cmdb.ciType.enumValue')">
|
||||
<template #header="{ column }">
|
||||
<span class="table-header-required">*</span>
|
||||
{{ column.title }}
|
||||
</template>
|
||||
<template #default="{ row, rowIndex }">
|
||||
<a-input
|
||||
v-if="enumValueType === ENUM_VALUE_TYPE.INPUT"
|
||||
:value="row.value"
|
||||
:placeholder="$t('cmdb.ciType.valueInputTip')"
|
||||
@change="(e) => changeValue(rowIndex, e.target.value)"
|
||||
></a-input>
|
||||
<a-input-number
|
||||
v-else-if="enumValueType === ENUM_VALUE_TYPE.NUMBER"
|
||||
:value="row.value"
|
||||
@change="(v) => changeValue(rowIndex, v)"
|
||||
>
|
||||
</a-input-number>
|
||||
<a-date-picker
|
||||
v-else
|
||||
style="width: 100%"
|
||||
:value="row.value"
|
||||
:format="enumValueType === ENUM_VALUE_TYPE.DATE ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
:showTime="enumValueType === ENUM_VALUE_TYPE.DATE ? false : { format: 'HH:mm:ss' }"
|
||||
@change="(e) => changeDate(rowIndex, e)"
|
||||
/>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column width="230" :title="$t('cmdb.ciType.label')">
|
||||
<template #default="{ row, rowIndex }">
|
||||
<DefineLabel
|
||||
:labelData="row"
|
||||
@change="(key, value) => changeStyle(rowIndex, key, value)"
|
||||
@deleteData="handleClear(rowIndex)"
|
||||
/>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<div class="define-wrap-action">
|
||||
<div
|
||||
v-for="(item, index) in defineList"
|
||||
:key="item.id"
|
||||
class="define-wrap-action-item"
|
||||
>
|
||||
<a-icon
|
||||
type="plus-circle"
|
||||
class="define-wrap-action-item-icon"
|
||||
@click="addData(index)"
|
||||
/>
|
||||
<a-icon
|
||||
type="minus-circle"
|
||||
class="define-wrap-action-item-icon"
|
||||
@click="deleteData(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import _ from 'lodash'
|
||||
import DefineLabel from './defineLabel.vue'
|
||||
import { ENUM_VALUE_TYPE } from '../constants.js'
|
||||
|
||||
export default {
|
||||
name: 'PreValueDefine',
|
||||
components: {
|
||||
DefineLabel
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 枚举值控件类型
|
||||
enumValueType: {
|
||||
type: String,
|
||||
default: ENUM_VALUE_TYPE.INPUT
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ENUM_VALUE_TYPE
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defineList: {
|
||||
get() {
|
||||
return this.value.map((item) => {
|
||||
return {
|
||||
value: item?.[0] ?? '',
|
||||
...(item?.[1] ?? {}),
|
||||
id: uuidv4()
|
||||
}
|
||||
})
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val.map((item) => {
|
||||
return [
|
||||
item?.value ?? '',
|
||||
{
|
||||
style: item?.style ?? {},
|
||||
icon: item?.icon ?? {},
|
||||
label: item?.label ?? '',
|
||||
}
|
||||
]
|
||||
}))
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addData(index) {
|
||||
const list = _.cloneDeep(this.value)
|
||||
list.splice(index + 1, 0, [
|
||||
'',
|
||||
{
|
||||
style: {},
|
||||
icon: {},
|
||||
label: ''
|
||||
}
|
||||
])
|
||||
this.$emit('change', list)
|
||||
},
|
||||
deleteData(index) {
|
||||
if (this.value.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ad.deleteTip'))
|
||||
return
|
||||
}
|
||||
const list = _.cloneDeep(this.value)
|
||||
list.splice(index, 1)
|
||||
this.$emit('change', list)
|
||||
},
|
||||
|
||||
changeValue(rowIndex, value) {
|
||||
const list = _.cloneDeep(this.value)
|
||||
list[rowIndex][0] = value
|
||||
this.$emit('change', list)
|
||||
},
|
||||
|
||||
changeDate(rowIndex, e) {
|
||||
const format = this.enumValueType === ENUM_VALUE_TYPE.DATE ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'
|
||||
const value = e.format(format)
|
||||
const list = _.cloneDeep(this.value)
|
||||
list[rowIndex][0] = value
|
||||
this.$emit('change', list)
|
||||
},
|
||||
|
||||
changeStyle(rowIndex, key, value) {
|
||||
const list = _.cloneDeep(this.value)
|
||||
list[rowIndex][1] = {
|
||||
...list[rowIndex][1],
|
||||
[key]: value
|
||||
}
|
||||
this.$emit('change', list)
|
||||
},
|
||||
|
||||
handleClear(rowIndex) {
|
||||
const list = _.cloneDeep(this.value)
|
||||
list[rowIndex][1] = {
|
||||
style: {},
|
||||
icon: {},
|
||||
label: ''
|
||||
}
|
||||
this.$emit('change', list)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.define-wrap {
|
||||
display: flex;
|
||||
|
||||
.add-btn {
|
||||
font-size: 12px;
|
||||
padding: 1px 7px;
|
||||
}
|
||||
|
||||
&-table {
|
||||
flex-shrink: 0;
|
||||
|
||||
.table-header-required {
|
||||
color: #FD4C6A;
|
||||
}
|
||||
|
||||
/deep/ .ant-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
flex-shrink: 0;
|
||||
margin-left: 11px;
|
||||
padding-top: 36px;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 46px;
|
||||
gap: 12px;
|
||||
|
||||
&-icon {
|
||||
cursor: pointer;
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -137,6 +137,7 @@ import {
|
||||
getCITypeChildren,
|
||||
getCITypeParent
|
||||
} from '../../api/CITypeRelation.js'
|
||||
import { getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
|
||||
export default {
|
||||
name: 'RelationAutoDiscovery',
|
||||
@@ -169,7 +170,18 @@ export default {
|
||||
methods: {
|
||||
async getCITypeAttributes() {
|
||||
const res = await getCITypeAttributes(this.CITypeId)
|
||||
this.ciTypeADTAttributes = res.map((item) => {
|
||||
const attr = await getCITypeAttributesById(this.CITypeId)
|
||||
|
||||
const filterAttr = res.filter((name) => {
|
||||
const currentAttr = attr?.attributes?.find((item) => item?.name === name)
|
||||
if (!currentAttr) {
|
||||
return true
|
||||
}
|
||||
|
||||
return this.filterAttributes(name)
|
||||
})
|
||||
|
||||
this.ciTypeADTAttributes = filterAttr.map((item) => {
|
||||
return {
|
||||
id: item,
|
||||
value: item,
|
||||
@@ -239,7 +251,7 @@ export default {
|
||||
const peer_type_id = item.peer_type_id
|
||||
const attributes = this?.relationOptions?.find((option) => option?.value === peer_type_id)?.attributes
|
||||
|
||||
item.attributes = attributes
|
||||
item.attributes = attributes.filter((attr) => this.filterAttributes(attr))
|
||||
item.peer_attr_id = undefined
|
||||
})
|
||||
},
|
||||
@@ -288,6 +300,15 @@ export default {
|
||||
this.getCITypeRelations()
|
||||
}
|
||||
},
|
||||
|
||||
filterAttributes(attr) {
|
||||
// filter password/json/is_list/longText/bool/reference
|
||||
if (attr?.value_type === '2' && !attr?.is_index) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !attr?.is_password && !attr?.is_list && attr?.value_type !== '6' && !attr?.is_bool && !attr?.is_reference
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,14 +1,16 @@
|
||||
<template>
|
||||
<div class="relation-table" :style="{ padding: '0 20px 20px' }">
|
||||
<a-button
|
||||
v-if="!isInGrantComp"
|
||||
style="margin-bottom: 10px"
|
||||
@click="handleCreate"
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="plus"
|
||||
>{{ $t('cmdb.ciType.addRelation') }}</a-button
|
||||
>
|
||||
<div v-if="!isInGrantComp" class="relation-table-add">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleCreate"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>
|
||||
<ops-icon type="veops-increase" />
|
||||
{{ $t('create') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
stripe
|
||||
@@ -179,6 +181,7 @@
|
||||
>
|
||||
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in CITypes">
|
||||
{{ CIType.alias || CIType.name }}
|
||||
<span class="model-select-name">({{ CIType.name }})</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
@@ -598,8 +601,14 @@ export default {
|
||||
})
|
||||
},
|
||||
filterAttributes(attributes) {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
// filter password/json/is_list/longText/bool/reference
|
||||
return attributes.filter((attr) => {
|
||||
if (attr.value_type === '2' && !attr.is_index) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !attr.is_password && !attr.is_list && attr.value_type !== '6' && !attr.is_bool && !attr.is_reference
|
||||
})
|
||||
},
|
||||
addTableAttr() {
|
||||
this.tableAttrList.push({
|
||||
@@ -646,6 +655,12 @@ export default {
|
||||
/deep/ .vxe-cell {
|
||||
max-height: max-content !important;
|
||||
}
|
||||
|
||||
&-add {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
.table-attribute-row {
|
||||
display: inline-flex;
|
||||
@@ -668,6 +683,11 @@ export default {
|
||||
.modal-attribute-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.model-select-name {
|
||||
font-size: 12px;
|
||||
color: #A5A9BC;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
|
@@ -471,10 +471,15 @@ export default {
|
||||
this.dateForm = _.cloneDeep(this.defaultDateForm)
|
||||
this.notifies = _.cloneDeep(this.defaultNotify)
|
||||
this.category = 1
|
||||
this.triggerAction = '1'
|
||||
this.filterExp = ''
|
||||
this.selectedBot = undefined
|
||||
this.visible = false
|
||||
if (this.$refs.noticeContent) {
|
||||
this.$refs.noticeContent.destroy()
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.visible = false
|
||||
})
|
||||
},
|
||||
filterChange(value) {
|
||||
this.filterValue = value
|
||||
|
@@ -1,13 +1,15 @@
|
||||
<template>
|
||||
<div class="ci-types-triggers">
|
||||
<div style="margin-bottom: 10px">
|
||||
<div class="ci-types-triggers-add">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleAddTrigger"
|
||||
size="small"
|
||||
icon="plus"
|
||||
>{{ $t('cmdb.ciType.newTrigger') }}</a-button
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>
|
||||
<ops-icon type="veops-increase" />
|
||||
{{ $t('create') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<ops-table
|
||||
stripe
|
||||
@@ -134,5 +136,11 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
.ci-types-triggers {
|
||||
padding: 0 20px 20px;
|
||||
|
||||
&-add {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -362,6 +362,7 @@ export default {
|
||||
commonAttributes: [],
|
||||
level2children: {},
|
||||
isShadow: false,
|
||||
changeCITypeRequestValue: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -406,7 +407,7 @@ export default {
|
||||
this.visible = true
|
||||
this.type = type
|
||||
this.item = item
|
||||
const { category = 0, name, type_id, attr_id, level } = item
|
||||
const { category = 0, name, type_id, level } = item
|
||||
const chartType = (item.options || {}).chartType || 'count'
|
||||
const fontColor = (item.options || {}).fontColor || '#ffffff'
|
||||
const bgColor = (item.options || {}).bgColor || ['#6ABFFE', '#5375EB']
|
||||
@@ -425,6 +426,14 @@ export default {
|
||||
this.fontColor = fontColor
|
||||
this.bgColor = bgColor
|
||||
}
|
||||
|
||||
if (type_ids?.length || type_id) {
|
||||
const requireTypeIds = type_id ? [type_id] : type_ids
|
||||
await getCITypeAttributesByTypeIds({ type_ids: requireTypeIds.join(',') }).then((res) => {
|
||||
this.attributes = res.attributes
|
||||
})
|
||||
}
|
||||
|
||||
if (type_ids && type_ids.length) {
|
||||
await getCITypeAttributesByTypeIds({ type_ids: type_ids.join(',') }).then((res) => {
|
||||
this.attributes = res.attributes
|
||||
@@ -483,19 +492,26 @@ export default {
|
||||
changeCIType(value) {
|
||||
this.form.attr_ids = []
|
||||
this.commonAttributes = []
|
||||
this.changeCITypeRequestValue = value
|
||||
if ((Array.isArray(value) && value.length) || (!Array.isArray(value) && value)) {
|
||||
getCITypeAttributesByTypeIds({ type_ids: Array.isArray(value) ? value.join(',') : value }).then((res) => {
|
||||
this.attributes = res.attributes
|
||||
if (this.changeCITypeRequestValue === value) {
|
||||
this.attributes = res.attributes
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!Array.isArray(value) && value) {
|
||||
getRecursive_level2children(value).then((res) => {
|
||||
this.level2children = res
|
||||
if (this.changeCITypeRequestValue === value) {
|
||||
this.level2children = res
|
||||
}
|
||||
})
|
||||
}
|
||||
if ((['bar', 'line', 'pie'].includes(this.chartType) && this.form.category === 1) || this.chartType === 'table') {
|
||||
getCITypeCommonAttributesByTypeIds({ type_ids: Array.isArray(value) ? value.join(',') : value }).then((res) => {
|
||||
this.commonAttributes = res.attributes
|
||||
if (this.changeCITypeRequestValue === value) {
|
||||
this.commonAttributes = res.attributes
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@@ -73,6 +73,7 @@ export const category_1_bar_options = (data, options) => {
|
||||
type: 'bar',
|
||||
stack: options?.barStack ?? 'total',
|
||||
barGap: 0,
|
||||
barMaxWidth: '16px',
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
@@ -242,6 +243,7 @@ export const category_2_bar_options = (data, options, chartType) => {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
barMaxWidth: '16px',
|
||||
areaStyle: chartType === 'line' && options?.isShadow ? {
|
||||
opacity: 0.5,
|
||||
color: {
|
||||
|
@@ -22,11 +22,11 @@
|
||||
<a-form-model-item :label="$t('name')" prop="name">
|
||||
<a-input v-model="form.name" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :label="$t('icon')" v-if="is_inner">
|
||||
<a-form-model-item :label="$t('icon')">
|
||||
<CustomIconSelect v-model="customIcon" :style="{ marginTop: '6px' }" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :label="$t('cmdb.ad.mode')" prop="is_plugin">
|
||||
<a-radio-group v-model="form.is_plugin" @change="changeIsPlugin" :disabled="!is_inner">
|
||||
<a-radio-group v-model="form.is_plugin" @change="changeIsPlugin" :disabled="true">
|
||||
<a-radio :value="false">{{ $t('cmdb.custom_dashboard.default') }}</a-radio>
|
||||
<a-radio :value="true">plugin</a-radio>
|
||||
</a-radio-group>
|
||||
@@ -137,9 +137,9 @@ export default {
|
||||
AgentTable
|
||||
},
|
||||
props: {
|
||||
is_inner: {
|
||||
isDiscoveryPage: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -152,7 +152,7 @@ export default {
|
||||
ruleData: {},
|
||||
type: 'add',
|
||||
adType: '',
|
||||
form: { name: '', is_plugin: false },
|
||||
form: { name: '', is_plugin: true },
|
||||
rules: {},
|
||||
customIcon: { name: '', color: '' },
|
||||
tableData: [],
|
||||
@@ -195,11 +195,9 @@ export default {
|
||||
this.type = type
|
||||
this.ruleData = data
|
||||
this.adType = adType
|
||||
if (!this.is_inner) {
|
||||
this.form = {
|
||||
name: '',
|
||||
is_plugin: true,
|
||||
}
|
||||
this.form = {
|
||||
name: '',
|
||||
is_plugin: true,
|
||||
}
|
||||
if (adType === DISCOVERY_CATEGORY_TYPE.HTTP || adType === DISCOVERY_CATEGORY_TYPE.SNMP) {
|
||||
return
|
||||
@@ -225,7 +223,7 @@ export default {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
this.plugin_script = this.default_plugin_script
|
||||
}
|
||||
if (data?.is_plugin || !this.is_inner) {
|
||||
if (this.form?.is_plugin) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.codemirror.initCodeMirror(this.plugin_script)
|
||||
})
|
||||
@@ -281,7 +279,7 @@ export default {
|
||||
const params = {
|
||||
...this.form,
|
||||
type,
|
||||
is_inner: this.is_inner,
|
||||
is_inner: !this.form.is_plugin,
|
||||
option: { icon: this.customIcon },
|
||||
attributes: this.form.is_plugin
|
||||
? undefined
|
||||
@@ -301,14 +299,14 @@ export default {
|
||||
this.type = 'edit'
|
||||
this.ruleData = res
|
||||
this.$message.success(this.$t('updateSuccess'))
|
||||
if (this.is_inner) {
|
||||
if (this.isDiscoveryPage) {
|
||||
this.getDiscovery()
|
||||
}
|
||||
return
|
||||
}
|
||||
this.handleClose()
|
||||
console.log(this.is_inner)
|
||||
if (this.is_inner) {
|
||||
|
||||
if (this.isDiscoveryPage) {
|
||||
this.$message.success(this.$t('saveSuccess'))
|
||||
this.getDiscovery()
|
||||
} else {
|
||||
|
@@ -78,7 +78,7 @@
|
||||
<p class="setting-discovery-empty-text">{{ $t('noData') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<EditDrawer ref="editDrawer" />
|
||||
<EditDrawer ref="editDrawer" :isDiscoveryPage="true" />
|
||||
<AccountConfig ref="accountConfig"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -344,6 +344,10 @@ export default {
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&_active {
|
||||
background-color: @primary-color_3;
|
||||
color: @primary-color;
|
||||
|
@@ -24,9 +24,10 @@
|
||||
@change="handleSourceTypeChange"
|
||||
:filterOption="filterOption"
|
||||
>
|
||||
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in displayCITypes">{{
|
||||
CIType.alias || CIType.name
|
||||
}}</a-select-option>
|
||||
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in displayCITypes">
|
||||
{{ CIType.alias || CIType.name }}
|
||||
<span class="model-select-name">({{ CIType.name }})</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.dstCIType')">
|
||||
@@ -39,6 +40,7 @@
|
||||
>
|
||||
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in displayTargetCITypes">
|
||||
{{ CIType.alias || CIType.name }}
|
||||
<span class="model-select-name">({{ CIType.name }})</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
@@ -129,7 +131,7 @@ import ModelRelationTable from './modules/modelRelationTable.vue'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { createRelation, deleteRelation, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
@@ -364,8 +366,14 @@ export default {
|
||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
filterAttributes(attributes) {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
// filter password/json/is_list/longText/bool/reference
|
||||
return attributes.filter((attr) => {
|
||||
if (attr.value_type === '2' && !attr.is_index) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !attr.is_password && !attr.is_list && attr.value_type !== '6' && !attr.is_bool && !attr.is_reference
|
||||
})
|
||||
},
|
||||
|
||||
addModalAttr() {
|
||||
@@ -402,4 +410,9 @@ export default {
|
||||
.modal-attribute-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.model-select-name {
|
||||
font-size: 12px;
|
||||
color: #A5A9BC;
|
||||
}
|
||||
</style>
|
||||
|
@@ -306,8 +306,14 @@ export default {
|
||||
return _find?.alias ?? _find?.name ?? id
|
||||
},
|
||||
filterAttributes(attributes) {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
// filter password/json/is_list/longText/bool/reference
|
||||
return attributes.filter((attr) => {
|
||||
if (attr.value_type === '2' && !attr.is_index) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !attr.is_password && !attr.is_list && attr.value_type !== '6' && !attr.is_bool && !attr.is_reference
|
||||
})
|
||||
},
|
||||
|
||||
addTableAttr() {
|
||||
|
@@ -163,204 +163,26 @@
|
||||
</div>
|
||||
</a-space>
|
||||
</SearchForm>
|
||||
<vxe-table
|
||||
:id="`cmdb-relation-${viewId}-${currentTypeId}`"
|
||||
border
|
||||
keep-source
|
||||
show-overflow
|
||||
resizable
|
||||
|
||||
<CITable
|
||||
ref="xTable"
|
||||
size="small"
|
||||
row-id="_id"
|
||||
:height="tableHeight"
|
||||
:id="`cmdb-relation-${viewId}-${currentTypeId}`"
|
||||
:loading="loading"
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
:attrList="preferenceAttrList"
|
||||
:columns="columns"
|
||||
:passwordValue="passwordValue"
|
||||
:data="instanceList"
|
||||
@checkbox-change="onSelectChange"
|
||||
@checkbox-all="onSelectChange"
|
||||
@checkbox-range-start="checkboxRangeStart"
|
||||
@checkbox-range-change="checkboxRangeChange"
|
||||
@checkbox-range-end="checkboxRangeEnd"
|
||||
:checkbox-config="{ reserve: true, range: true }"
|
||||
:height="tableHeight"
|
||||
:showCheckbox="isLeaf"
|
||||
:showDelete="isLeaf"
|
||||
@onSelectChange="onSelectChange"
|
||||
@edit-closed="handleEditClose"
|
||||
@edit-actived="handleEditActived"
|
||||
:edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }"
|
||||
:sort-config="{ remote: true, trigger: 'cell' }"
|
||||
@sort-change="handleSortCol"
|
||||
:row-key="true"
|
||||
:column-key="true"
|
||||
:cell-style="getCellStyle"
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:scroll-x="{ enabled: true, gt: 0 }"
|
||||
class="ops-unstripe-table checkbox-hover-table"
|
||||
:custom-config="{ storage: true }"
|
||||
>
|
||||
<vxe-column v-if="isLeaf" align="center" type="checkbox" width="50" fixed="left">
|
||||
<template #default="{row}">
|
||||
{{ getRowSeq(row) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-table-column
|
||||
v-for="(col, index) in columns"
|
||||
:key="`${col.field}_${index}`"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:sortable="col.sortable"
|
||||
:edit-render="getColumnsEditRender(col)"
|
||||
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
|
||||
:fixed="col.is_fixed ? 'left' : ''"
|
||||
>
|
||||
<template #header>
|
||||
<span class="vxe-handle">
|
||||
<OpsMoveIcon
|
||||
style="width: 17px; height: 17px; display: none; position: absolute; left: -3px; top: 12px"
|
||||
/>
|
||||
{{ col.title }}</span
|
||||
>
|
||||
</template>
|
||||
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
|
||||
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
|
||||
<a-select
|
||||
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||
:style="{ width: '100%', height: '32px' }"
|
||||
v-model="row[col.field]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-if="col.is_choice"
|
||||
:showArrow="false"
|
||||
:mode="col.is_list ? 'multiple' : 'default'"
|
||||
class="ci-table-edit-select"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="choice[0]"
|
||||
:key="'edit_' + col.field + idx"
|
||||
v-for="(choice, idx) in col.filters"
|
||||
>
|
||||
<span
|
||||
:style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }"
|
||||
>
|
||||
<template v-if="choice[1] && choice[1].icon && choice[1].icon.name">
|
||||
<img
|
||||
v-if="choice[1].icon.id && choice[1].icon.url"
|
||||
:src="`/api/common-setting/v1/file/${choice[1].icon.url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: choice[1].icon.color, marginRight: '5px' }"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
</template>
|
||||
{{ choice[0] }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template
|
||||
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
|
||||
#default="{row}"
|
||||
>
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
|
||||
<a
|
||||
v-else-if="col.is_link && row[col.field]"
|
||||
:href="
|
||||
row[col.field].startsWith('http') || row[col.field].startsWith('https')
|
||||
? `${row[col.field]}`
|
||||
: `http://${row[col.field]}`
|
||||
"
|
||||
target="_blank"
|
||||
>{{ row[col.field] }}</a
|
||||
>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
:attr_id="col.attr_id"
|
||||
></PasswordField>
|
||||
<template v-else-if="col.is_choice">
|
||||
<template v-if="col.is_list">
|
||||
<span
|
||||
v-for="value in row[col.field]"
|
||||
:key="value"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px',
|
||||
...getChoiceValueStyle(col, value),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="getChoiceValueIcon(col, value).id && getChoiceValueIcon(col, value).url"
|
||||
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(col, value).url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: getChoiceValueIcon(col, value).color, marginRight: '5px' }"
|
||||
:type="getChoiceValueIcon(col, value).name"
|
||||
/>{{ value }}</span
|
||||
>
|
||||
</template>
|
||||
<span
|
||||
v-else-if="row[col.field]"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px 0',
|
||||
...getChoiceValueStyle(col, row[col.field]),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="getChoiceValueIcon(col, row[col.field]).id && getChoiceValueIcon(col, row[col.field]).url"
|
||||
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(col, row[col.field]).url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: getChoiceValueIcon(col, row[col.field]).color, marginRight: '5px' }"
|
||||
:type="getChoiceValueIcon(col, row[col.field]).name"
|
||||
/>{{ row[col.field] }}</span
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-column align="left" field="operate" fixed="right" width="80">
|
||||
<template #header>
|
||||
<span>{{ $t('operation') }}</span>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a @click="$refs.detail.create(row.ci_id || row._id)">
|
||||
<a-icon type="unordered-list" />
|
||||
</a>
|
||||
<a-tooltip :title="$t('cmdb.ci.addRelation')">
|
||||
<a @click="$refs.detail.create(row.ci_id || row._id, 'tab_2', '2')">
|
||||
<a-icon type="retweet" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<template v-if="isLeaf">
|
||||
<a-tooltip :title="$t('cmdb.ciType.deleteInstance')">
|
||||
<a @click="deleteCI(row)" :style="{ color: 'red' }">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div v-if="loading" style="height: 200px; line-height: 200px">{{ $t('loading') }}</div>
|
||||
<div v-else>
|
||||
<img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-table>
|
||||
@openDetail="openDetail"
|
||||
@deleteCI="deleteCI"
|
||||
/>
|
||||
|
||||
<div :style="{ textAlign: 'right', marginTop: '4px' }">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
@@ -405,7 +227,6 @@
|
||||
@reload="sumbitFromCreateInstance"
|
||||
@submit="batchUpdateFromCreateInstance"
|
||||
/>
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<ReadPermissionsModal ref="readPermissionsModal" />
|
||||
<RevokeModal ref="revokeModal" @handleRevoke="handleRevoke" />
|
||||
@@ -440,16 +261,14 @@ import SplitPane from '@/components/SplitPane'
|
||||
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
||||
import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
|
||||
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
||||
import JsonEditor from '../../components/JsonEditor/jsonEditor.vue'
|
||||
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
|
||||
import PasswordField from '../../components/passwordField/index.vue'
|
||||
import PreferenceSearch from '../../components/preferenceSearch/preferenceSearch.vue'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
import GrantModal from '../../components/cmdbGrant/grantModal.vue'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import { getAttrPassword } from '../../api/CITypeAttr'
|
||||
import ReadPermissionsModal from './modules/ReadPermissionsModal.vue'
|
||||
import RevokeModal from '../../components/cmdbGrant/revokeModal.vue'
|
||||
import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'RelationViews',
|
||||
@@ -464,13 +283,11 @@ export default {
|
||||
EditAttrsPopover,
|
||||
CiDetailDrawer,
|
||||
CreateInstanceForm,
|
||||
JsonEditor,
|
||||
BatchDownload,
|
||||
PasswordField,
|
||||
PreferenceSearch,
|
||||
OpsMoveIcon,
|
||||
ReadPermissionsModal,
|
||||
RevokeModal,
|
||||
CITable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -635,7 +452,7 @@ export default {
|
||||
refreshTable() {
|
||||
this.selectedRowKeys = []
|
||||
this.sortByTable = undefined
|
||||
const xTable = this.$refs.xTable
|
||||
const xTable = this.$refs.xTable.getVxetableRef()
|
||||
if (xTable) {
|
||||
xTable.clearCheckboxRow()
|
||||
xTable.clearCheckboxReserve()
|
||||
@@ -771,7 +588,7 @@ export default {
|
||||
this.calcColumns()
|
||||
}
|
||||
if (refreshType === 'refreshNumber') {
|
||||
const promises = this.treeKeys.map((key, index) => {
|
||||
this.treeKeys.map((key, index) => {
|
||||
let ancestor_ids
|
||||
if (
|
||||
Object.keys(this.level2constraint).some(
|
||||
@@ -815,8 +632,8 @@ export default {
|
||||
},
|
||||
|
||||
changeCIType(typeId) {
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.$refs.xTable.clearCheckboxReserve()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.$refs.search.reset()
|
||||
this.selectedRowKeys = []
|
||||
this.currentTypeId = [typeId]
|
||||
@@ -983,8 +800,8 @@ export default {
|
||||
if (keys) {
|
||||
const _tempKeys = keys.split('@^@').filter((item) => item !== '')
|
||||
if (_tempKeys.length === this.levels.length) {
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.$refs.xTable.clearCheckboxReserve()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.selectedRowKeys = []
|
||||
}
|
||||
this.treeKeys = _tempKeys
|
||||
@@ -1073,7 +890,10 @@ export default {
|
||||
this.currentView = `${this.viewId}`
|
||||
this.typeId = this.levels[0][0]
|
||||
this.viewOption = this.relationViews.views[this.viewName].option ?? {}
|
||||
this.refreshTable()
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.refreshTable()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -1097,7 +917,7 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.$refs.xTable.refreshColumn()
|
||||
this.$refs.xTable.getVxetableRef().refreshColumn()
|
||||
})
|
||||
},
|
||||
calculateParamsFromTreeKey(treeKey, menuKey) {
|
||||
@@ -1167,23 +987,8 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
onSelectChange({ records, reserves }) {
|
||||
this.selectedRowKeys = [...records, ...reserves]
|
||||
},
|
||||
checkboxRangeStart(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const lastSelected = xTable.getCheckboxRecords()
|
||||
const selectedReserve = xTable.getCheckboxReserveRecords()
|
||||
this.lastSelected = [...lastSelected, ...selectedReserve]
|
||||
},
|
||||
checkboxRangeChange(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
xTable.setCheckboxRow(this.lastSelected, true)
|
||||
},
|
||||
checkboxRangeEnd(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
this.lastSelected = []
|
||||
this.selectedRowKeys = [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()]
|
||||
onSelectChange(records) {
|
||||
this.selectedRowKeys = records
|
||||
},
|
||||
batchDeleteCIRelation() {
|
||||
const currentShowType = this.showTypes.find((item) => item.id === Number(this.currentTypeId[0]))
|
||||
@@ -1214,8 +1019,8 @@ export default {
|
||||
[first_ci_id],
|
||||
ancestor_ids
|
||||
).then((res) => {
|
||||
that.$refs.xTable.clearCheckboxRow()
|
||||
that.$refs.xTable.clearCheckboxReserve()
|
||||
that.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
that.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
that.selectedRowKeys = []
|
||||
that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
})
|
||||
@@ -1273,7 +1078,7 @@ export default {
|
||||
},
|
||||
columnDrop() {
|
||||
this.$nextTick(() => {
|
||||
const xTable = this.$refs.xTable
|
||||
const xTable = this.$refs.xTable.getVxetableRef()
|
||||
this.sortable = Sortable.create(
|
||||
xTable.$el.querySelector('.body--wrapper>.vxe-table--header .vxe-header--row'),
|
||||
{
|
||||
@@ -1305,49 +1110,13 @@ export default {
|
||||
)
|
||||
})
|
||||
},
|
||||
getChoiceValueStyle(col, colValue) {
|
||||
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.style || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
getChoiceValueIcon(col, colValue) {
|
||||
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.icon || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
getCellStyle({ row, rowIndex, $rowIndex, column, columnIndex, $columnIndex }) {
|
||||
const { property } = column
|
||||
const _find = this.preferenceAttrList.find((attr) => attr.name === property)
|
||||
if (
|
||||
_find &&
|
||||
_find.option &&
|
||||
_find.option.fontOptions &&
|
||||
row[`${property}`] !== undefined &&
|
||||
row[`${property}`] !== null
|
||||
) {
|
||||
return { ..._find.option.fontOptions }
|
||||
}
|
||||
},
|
||||
refreshAfterEditAttrs() {
|
||||
this.loadColumns()
|
||||
},
|
||||
getColumnsEditRender(col) {
|
||||
const _editRender = {
|
||||
...col.editRender,
|
||||
}
|
||||
if (col.value_type === '6') {
|
||||
_editRender.events = { focus: this.handleFocusJson }
|
||||
}
|
||||
return _editRender
|
||||
},
|
||||
handleEditActived() {
|
||||
const passwordCol = this.columns.filter((col) => col.is_password)
|
||||
this.$nextTick(() => {
|
||||
const editRecord = this.$refs.xTable.getEditRecord()
|
||||
const editRecord = this.$refs.xTable.getVxetableRef().getEditRecord()
|
||||
const { row, column } = editRecord
|
||||
if (passwordCol.length && this.lastEditCiId !== row._id) {
|
||||
this.$nextTick(async () => {
|
||||
@@ -1358,10 +1127,10 @@ export default {
|
||||
})
|
||||
}
|
||||
this.isContinueCloseEdit = false
|
||||
await this.$refs.xTable.clearEdit()
|
||||
await this.$refs.xTable.getVxetableRef().clearEdit()
|
||||
this.isContinueCloseEdit = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.xTable.setEditCell(row, column.field)
|
||||
this.$refs.xTable.getVxetableRef().setEditCell(row, column.field)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1372,7 +1141,7 @@ export default {
|
||||
if (!this.isContinueCloseEdit) {
|
||||
return
|
||||
}
|
||||
const $table = this.$refs['xTable']
|
||||
const $table = this.$refs['xTable'].getVxetableRef()
|
||||
const data = {}
|
||||
this.columns.forEach((item) => {
|
||||
if (
|
||||
@@ -1490,7 +1259,7 @@ export default {
|
||||
},
|
||||
async openBatchDownload() {
|
||||
this.$refs.batchDownload.open({
|
||||
preferenceAttrList: this.preferenceAttrList,
|
||||
preferenceAttrList: this.preferenceAttrList.filter((attr) => !attr?.is_reference),
|
||||
ciTypeName: this.$route.meta.name,
|
||||
})
|
||||
},
|
||||
@@ -1501,10 +1270,10 @@ export default {
|
||||
if (_find && _find.value_type === '6') jsonAttrList.push(key)
|
||||
})
|
||||
const data = _.cloneDeep([
|
||||
...this.$refs.xTable.getCheckboxReserveRecords(),
|
||||
...this.$refs.xTable.getCheckboxRecords(true),
|
||||
...this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords(),
|
||||
...this.$refs.xTable.getVxetableRef().getCheckboxRecords(true),
|
||||
])
|
||||
this.$refs.xTable.exportData({
|
||||
this.$refs.xTable.getVxetableRef().exportData({
|
||||
filename,
|
||||
type,
|
||||
columnFilterMethod({ column }) {
|
||||
@@ -1518,8 +1287,8 @@ export default {
|
||||
],
|
||||
})
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.$refs.xTable.clearCheckboxReserve()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
},
|
||||
batchDelete() {
|
||||
const that = this
|
||||
@@ -1543,25 +1312,13 @@ export default {
|
||||
.finally(() => {
|
||||
that.loading = false
|
||||
that.selectedRowKeys = []
|
||||
that.$refs.xTable.clearCheckboxRow()
|
||||
that.$refs.xTable.clearCheckboxReserve()
|
||||
that.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
that.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
that.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
handleFocusJson({ column, row }) {
|
||||
this.$refs.jsonEditor.open(column, row)
|
||||
},
|
||||
jsonEditorOk(row, column, jsonData) {
|
||||
// 后端写数据有快慢,不拉接口直接修改table的数据
|
||||
this.instanceList.forEach((item) => {
|
||||
if (item._id === row._id) {
|
||||
item[column.property] = JSON.stringify(jsonData)
|
||||
}
|
||||
})
|
||||
this.$refs.xTable.refreshColumn()
|
||||
},
|
||||
relationViewRefreshNumber() {
|
||||
this.loadData({ parameter: {}, refreshType: 'refreshNumber' })
|
||||
},
|
||||
@@ -1585,7 +1342,9 @@ export default {
|
||||
})
|
||||
},
|
||||
setPreferenceSearchCurrent(id = null) {
|
||||
this.$refs.preferenceSearch.currentPreferenceSearch = id
|
||||
this.$nextTick(() => {
|
||||
this.$refs.preferenceSearch.currentPreferenceSearch = id
|
||||
})
|
||||
},
|
||||
copyExpression() {
|
||||
const expression = this.$refs['search'].expression || ''
|
||||
@@ -1673,7 +1432,7 @@ export default {
|
||||
content: (h) => <div>{that.$t('confirmDelete')}</div>,
|
||||
async onOk() {
|
||||
for (let i = 0; i < that.batchTreeKey.length; i++) {
|
||||
const { splitTreeKey, firstCIObj, firstCIId, _tempTree, ancestor_ids } = that.calculateParamsFromTreeKey(
|
||||
const { splitTreeKey, _tempTree, ancestor_ids } = that.calculateParamsFromTreeKey(
|
||||
that.batchTreeKey[i],
|
||||
'delete'
|
||||
)
|
||||
@@ -1846,8 +1605,8 @@ export default {
|
||||
return array
|
||||
},
|
||||
|
||||
getRowSeq(row) {
|
||||
return this.$refs.xTable.getRowSeq(row)
|
||||
openDetail(id, activeTabKey, ciDetailRelationKey) {
|
||||
this.$refs.detail.create(id, activeTabKey, ciDetailRelationKey)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1916,36 +1675,6 @@ export default {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
|
||||
.checkbox-hover-table {
|
||||
.vxe-table--body-wrapper {
|
||||
.vxe-checkbox--label {
|
||||
display: inline;
|
||||
padding-left: 0px !important;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.vxe-icon-checkbox-unchecked {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vxe-cell--checkbox {
|
||||
&:hover {
|
||||
.vxe-icon-checkbox-unchecked {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.vxe-checkbox--label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -53,8 +53,26 @@
|
||||
:width="col.width"
|
||||
:sortable="col.sortable"
|
||||
>
|
||||
<template #default="{row}" v-if="col.value_type === '6'">
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
<template v-if="col.is_reference" #default="{row}">
|
||||
<a
|
||||
v-for="(id) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="id"
|
||||
:href="`/cmdb/cidetail/${col.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ id }}
|
||||
</a>
|
||||
</template>
|
||||
<template #default="{row}" v-else-if="col.is_choice">
|
||||
<span
|
||||
v-for="value in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="value"
|
||||
>
|
||||
{{ getChoiceValueLabel(col, value) || value }}
|
||||
</span>
|
||||
</template>
|
||||
<template #default="{row}" v-else-if="col.value_type == '6'">
|
||||
<span v-if="col.value_type == '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
@@ -243,6 +261,14 @@ export default {
|
||||
this.currentPage = page
|
||||
this.fetchData()
|
||||
},
|
||||
|
||||
getChoiceValueLabel(col, colValue) {
|
||||
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
|
||||
if (_find) {
|
||||
return _find[1]?.label || ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -48,7 +48,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ciTypeFilterPermissions, getCIType } from '../../../api/CIType'
|
||||
import { ciTypeFilterPermissions } from '../../../api/CIType'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user