Compare commits

..

7 Commits

95 changed files with 696 additions and 1016 deletions

View File

@@ -32,7 +32,7 @@ from api.lib.perm.acl.resource import ResourceCRUD
from api.lib.perm.acl.resource import ResourceTypeCRUD
from api.lib.perm.acl.role import RoleCRUD
from api.lib.secrets.inner import KeyManage
from api.lib.secrets.inner import global_key_threshold
from api.lib.secrets.inner import global_key_threshold, secrets_shares
from api.lib.secrets.secrets import InnerKVManger
from api.models.acl import App
from api.models.acl import ResourceType
@@ -55,12 +55,9 @@ def cmdb_init_cache():
for cr in ci_relations:
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
if cr.ancestor_ids:
relations2.setdefault('{},{}'.format(cr.ancestor_ids, cr.first_ci_id), {}).update(
{cr.second_ci_id: cr.second_ci.type_id})
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
for i in relations:
relations[i] = json.dumps(relations[i])
for i in relations2:
relations2[i] = json.dumps(relations2[i])
if relations:
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
if relations2:

View File

@@ -6,7 +6,6 @@ from werkzeug.datastructures import MultiDict
from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.common_setting.utils import CheckNewColumn
from api.models.common_setting import Employee, Department
@@ -210,7 +209,57 @@ def common_check_new_columns():
"""
add new columns to tables
"""
CheckNewColumn().run()
from api.extensions import db
from sqlalchemy import inspect, text
def get_model_by_table_name(_table_name):
registry = getattr(db.Model, 'registry', None)
class_registry = getattr(registry, '_class_registry', None)
for _model in class_registry.values():
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
return _model
return None
def add_new_column(target_table_name, new_column):
column_type = new_column.type.compile(engine.dialect)
default_value = new_column.default.arg if new_column.default else None
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
if new_column.comment:
sql += f" comment '{new_column.comment}'"
if column_type == 'JSON':
pass
elif default_value:
if column_type.startswith('VAR') or column_type.startswith('Text'):
if default_value is None or len(default_value) == 0:
pass
else:
sql += f" DEFAULT {default_value}"
sql = text(sql)
db.session.execute(sql)
engine = db.get_engine()
inspector = inspect(engine)
table_names = inspector.get_table_names()
for table_name in table_names:
existed_columns = inspector.get_columns(table_name)
existed_column_name_list = [c['name'] for c in existed_columns]
model = get_model_by_table_name(table_name)
if model is None:
continue
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
for column in model_columns:
if column.name not in existed_column_name_list:
try:
add_new_column(table_name, column)
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
except Exception as e:
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
current_app.logger.error(e)
@click.command()

View File

@@ -92,9 +92,6 @@ class CITypeManager(object):
for type_dict in ci_types:
attr = AttributeCache.get(type_dict["unique_id"])
type_dict["unique_key"] = attr and attr.name
if type_dict.get('show_id'):
attr = AttributeCache.get(type_dict["show_id"])
type_dict["show_name"] = attr and attr.name
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
if resources is None or type_dict['name'] in resources:
res.append(type_dict)
@@ -195,7 +192,7 @@ class CITypeManager(object):
CITypeAttributeManager.update(type_id, [attr])
ci_type2 = ci_type.to_dict()
new = ci_type.update(**kwargs, filter_none=False)
new = ci_type.update(**kwargs)
CITypeCache.clean(type_id)
@@ -683,9 +680,6 @@ class CITypeAttributeManager(object):
CITypeAttributeCache.clean(type_id, attr_id)
if ci_type.show_id == attr_id:
ci_type.update(show_id=None, filter_none=False)
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
change=attr and attr.to_dict())
@@ -1233,10 +1227,7 @@ class CITypeTemplateManager(object):
def _import_ci_types(self, ci_types, attr_id_map):
for i in ci_types:
i.pop("unique_key", None)
i.pop("show_name", None)
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
if i.get('show_id'):
i['show_id'] = attr_id_map.get(i['show_id'], i['show_id'])
i['uid'] = current_user.uid
return self.__import(CIType, ci_types)

View File

@@ -297,10 +297,6 @@ class PreferenceManager(object):
for type_id in id2type:
id2type[type_id] = CITypeCache.get(type_id).to_dict()
id2type[type_id]['unique_name'] = AttributeCache.get(id2type[type_id]['unique_id']).name
if id2type[type_id]['show_id']:
show_attr = AttributeCache.get(id2type[type_id]['show_id'])
id2type[type_id]['show_name'] = show_attr and show_attr.name
return result, id2type, sorted(name2id, key=lambda x: x[1])

View File

@@ -8,8 +8,6 @@ from flask import current_app
from flask_login import current_user
from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import ConstraintEnum
@@ -20,8 +18,6 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
from api.lib.cmdb.utils import TableMap
from api.lib.cmdb.utils import ValueTypeMap
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin
from api.models.cmdb import CI
@@ -240,7 +236,7 @@ class Search(object):
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
_tmp, tmp_res = [], []
_tmp = []
level2ids = {}
for lv in range(1, self.level + 1):
level2ids[lv] = []
@@ -307,26 +303,25 @@ class Search(object):
if key:
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
if type_ids and lv == self.level:
tmp_res = [[i for i in x if i[1] in type_ids and
(not id_filter_limit or (
key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
__tmp = [[i for i in x if i[1] in type_ids and
(not id_filter_limit or (
key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
else:
tmp_res = [[i for i in x if (not id_filter_limit or (
__tmp = [[i for i in x if (not id_filter_limit or (
key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in
enumerate(res)]
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
if ci_filter_limit:
tmp_res = [[j for j in i if j[1] not in ci_filter_limit or
int(j[0]) in ci_filter_limit[j[1]]] for i in tmp_res]
__tmp = [[j for j in i if j[1] not in ci_filter_limit or
int(j[0]) in ci_filter_limit[j[1]]] for i in __tmp]
else:
tmp_res = []
__tmp = []
if tmp_res:
_tmp[idx] = [j for i in tmp_res for j in i]
if __tmp:
_tmp[idx] = [j for i in __tmp for j in i]
else:
_tmp[idx] = []
level2ids[lv].append([])
@@ -337,84 +332,3 @@ class Search(object):
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
return result
def search_full(self, type_ids):
def _get_id2name(_type_id):
ci_type = CITypeCache.get(_type_id)
attr = AttributeCache.get(ci_type.unique_id)
value_table = TableMap(attr=attr).table
serializer = ValueTypeMap.serialize[attr.value_type]
unique_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
attr = AttributeCache.get(ci_type.show_id)
if attr:
value_table = TableMap(attr=attr).table
serializer = ValueTypeMap.serialize[attr.value_type]
show_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
else:
show_value = unique_value
return show_value, unique_value
self.level = int(self.level)
acl = ACLManager('cmdb')
type2filter_perms = dict()
if not self.is_app_admin:
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
if res2:
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
level_ids = [str(i) for i in ids]
result = []
id2children = {}
id2name = _get_id2name(type_ids[0])
for i in level_ids:
item = dict(id=int(i),
type_id=type_ids[0],
isLeaf=False,
title=id2name[0].get(int(i)),
uniqueValue=id2name[1].get(int(i)),
children=[])
result.append(item)
id2children[str(i)] = item['children']
for lv in range(1, self.level):
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
else:
id_filter_limit = {}
if self.has_m2m and lv != 1:
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
else:
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
res = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
_level_ids = []
type_id = type_ids[lv]
id2name = _get_id2name(type_id)
for idx, node_path in enumerate(level_ids):
for child_id, _ in (res[idx] or []):
item = dict(id=int(child_id),
type_id=type_id,
isLeaf=True if lv == self.level - 1 else False,
title=id2name[0].get(int(child_id)),
uniqueValue=id2name[1].get(int(child_id)),
children=[])
id2children[node_path].append(item)
_node_path = "{},{}".format(node_path, child_id)
_level_ids.append(_node_path)
id2children[_node_path] = item['children']
level_ids = _level_ids
return result

View File

@@ -1,10 +1,5 @@
# -*- coding:utf-8 -*-
from datetime import datetime
from flask import current_app
from sqlalchemy import inspect, text
from sqlalchemy.dialects.mysql import ENUM
from api.extensions import db
def get_cur_time_str(split_flag='-'):
@@ -28,115 +23,3 @@ class BaseEnum(object):
if not attr.startswith("_") and not callable(getattr(cls, attr))
}
return cls._ALL_
class CheckNewColumn(object):
def __init__(self):
self.engine = db.get_engine()
self.inspector = inspect(self.engine)
self.table_names = self.inspector.get_table_names()
@staticmethod
def get_model_by_table_name(_table_name):
registry = getattr(db.Model, 'registry', None)
class_registry = getattr(registry, '_class_registry', None)
for _model in class_registry.values():
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
return _model
return None
def run(self):
for table_name in self.table_names:
self.check_by_table(table_name)
def check_by_table(self, table_name):
existed_columns = self.inspector.get_columns(table_name)
enum_columns = []
existed_column_name_list = []
for c in existed_columns:
if isinstance(c['type'], ENUM):
enum_columns.append(c['name'])
existed_column_name_list.append(c['name'])
model = self.get_model_by_table_name(table_name)
if model is None:
return
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
for column in model_columns:
if column.name not in existed_column_name_list:
add_res = self.add_new_column(table_name, column)
if not add_res:
continue
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
if column.name in enum_columns:
enum_columns.remove(column.name)
self.add_new_index(table_name, column)
if len(enum_columns) > 0:
self.check_enum_column(enum_columns, existed_columns, model_columns, table_name)
def add_new_column(self, target_table_name, new_column):
try:
column_type = new_column.type.compile(self.engine.dialect)
default_value = new_column.default.arg if new_column.default else None
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
if new_column.comment:
sql += f" comment '{new_column.comment}'"
if column_type == 'JSON':
pass
elif default_value:
if column_type.startswith('VAR') or column_type.startswith('Text'):
if default_value is None or len(default_value) == 0:
pass
else:
sql += f" DEFAULT {default_value}"
sql = text(sql)
db.session.execute(sql)
return True
except Exception as e:
err = f"add_new_column [{new_column.name}] to table [{target_table_name}] err: {e}"
current_app.logger.error(err)
return False
@staticmethod
def add_new_index(target_table_name, new_column):
try:
if new_column.index:
index_name = f"{target_table_name}_{new_column.name}"
sql = "CREATE INDEX " + f"{index_name}" + " ON " + target_table_name + " (" + new_column.name + ")"
db.session.execute(sql)
current_app.logger.info(f"add new index [{index_name}] in table [{target_table_name}] success.")
return True
except Exception as e:
err = f"add_new_index [{new_column.name}] to table [{target_table_name}] err: {e}"
current_app.logger.error(err)
return False
@staticmethod
def check_enum_column(enum_columns, existed_columns, model_columns, table_name):
for column_name in enum_columns:
try:
enum_column = list(filter(lambda x: x['name'] == column_name, existed_columns))[0]
old_enum_value = enum_column.get('type', {}).enums
target_column = list(filter(lambda x: x.name == column_name, model_columns))[0]
new_enum_value = target_column.type.enums
if set(old_enum_value) == set(new_enum_value):
continue
enum_values_str = ','.join(["'{}'".format(value) for value in new_enum_value])
sql = f"ALTER TABLE {table_name} MODIFY COLUMN" + f"`{column_name}`" + f" enum({enum_values_str})"
db.session.execute(sql)
current_app.logger.info(
f"modify column [{column_name}] ENUM: {new_enum_value} in table [{table_name}] success.")
except Exception as e:
current_app.logger.error(
f"modify column ENUM [{column_name}] in table [{table_name}] err: {e}")

View File

@@ -46,17 +46,13 @@ class CIType(Model):
name = db.Column(db.String(32), nullable=False)
alias = db.Column(db.String(32), nullable=False)
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
show_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
enabled = db.Column(db.Boolean, default=True, nullable=False)
is_attached = db.Column(db.Boolean, default=False, nullable=False)
icon = db.Column(db.Text)
order = db.Column(db.SmallInteger, default=0, nullable=False)
default_order_attr = db.Column(db.String(33))
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id",
primaryjoin="Attribute.id==CIType.unique_id", foreign_keys=[unique_id])
show_key = db.relationship("Attribute", backref="c_ci_types.show_id",
primaryjoin="Attribute.id==CIType.show_id", foreign_keys=[show_id])
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id")
uid = db.Column(db.Integer, index=True)

View File

@@ -30,7 +30,6 @@ class CIRelationSearchView(APIView):
level: default is 1
facet: statistic
"""
page = get_page(request.values.get("page", 1))
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
@@ -87,26 +86,6 @@ class CIRelationStatisticsView(APIView):
return self.jsonify(result)
class CIRelationSearchFullView(APIView):
url_prefix = "/ci_relations/search/full"
def get(self):
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
level = request.values.get('level', 1)
type_ids = list(map(int, handle_arg_list(request.values.get('type_ids', []))))
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
start = time.time()
s = Search(root_ids, level, has_m2m=has_m2m)
try:
result = s.search_full(type_ids)
except SearchError as e:
return abort(400, str(e))
current_app.logger.debug("search time is :{0}".format(time.time() - start))
return self.jsonify(result)
class GetSecondCIsView(APIView):
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"

View File

@@ -42,11 +42,9 @@
"relation-graph": "^1.1.0",
"snabbdom": "^3.5.1",
"sortablejs": "1.9.0",
"style-resources-loader": "^1.5.0",
"viser-vue": "^2.4.8",
"vue": "2.6.11",
"vue-clipboard2": "^0.3.3",
"vue-cli-plugin-style-resources-loader": "^0.1.5",
"vue-codemirror": "^4.0.6",
"vue-cropper": "^0.6.2",
"vue-grid-layout": "2.3.12",

View File

@@ -54,24 +54,6 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe914;</span>
<div class="name">veops-show</div>
<div class="code-name">&amp;#xe914;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe913;</span>
<div class="name">itsm-duration</div>
<div class="code-name">&amp;#xe913;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe912;</span>
<div class="name">itsm-workload (1)</div>
<div class="code-name">&amp;#xe912;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe910;</span>
<div class="name">VPC</div>
@@ -4806,9 +4788,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1713335725699') format('woff2'),
url('iconfont.woff?t=1713335725699') format('woff'),
url('iconfont.ttf?t=1713335725699') format('truetype');
src: url('iconfont.woff2?t=1711963254221') format('woff2'),
url('iconfont.woff?t=1711963254221') format('woff'),
url('iconfont.ttf?t=1711963254221') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -4834,33 +4816,6 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont veops-show"></span>
<div class="name">
veops-show
</div>
<div class="code-name">.veops-show
</div>
</li>
<li class="dib">
<span class="icon iconfont itsm-duration"></span>
<div class="name">
itsm-duration
</div>
<div class="code-name">.itsm-duration
</div>
</li>
<li class="dib">
<span class="icon iconfont a-itsm-workload1"></span>
<div class="name">
itsm-workload (1)
</div>
<div class="code-name">.a-itsm-workload1
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-VPC"></span>
<div class="name">
@@ -11962,30 +11917,6 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-show"></use>
</svg>
<div class="name">veops-show</div>
<div class="code-name">#veops-show</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#itsm-duration"></use>
</svg>
<div class="name">itsm-duration</div>
<div class="code-name">#itsm-duration</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-itsm-workload1"></use>
</svg>
<div class="name">itsm-workload (1)</div>
<div class="code-name">#a-itsm-workload1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-VPC"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1713335725699') format('woff2'),
url('iconfont.woff?t=1713335725699') format('woff'),
url('iconfont.ttf?t=1713335725699') format('truetype');
src: url('iconfont.woff2?t=1711963254221') format('woff2'),
url('iconfont.woff?t=1711963254221') format('woff'),
url('iconfont.ttf?t=1711963254221') format('truetype');
}
.iconfont {
@@ -13,18 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
.veops-show:before {
content: "\e914";
}
.itsm-duration:before {
content: "\e913";
}
.a-itsm-workload1:before {
content: "\e912";
}
.caise-VPC:before {
content: "\e910";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,27 +5,6 @@
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "39948814",
"name": "veops-show",
"font_class": "veops-show",
"unicode": "e914",
"unicode_decimal": 59668
},
{
"icon_id": "39926816",
"name": "itsm-duration",
"font_class": "itsm-duration",
"unicode": "e913",
"unicode_decimal": 59667
},
{
"icon_id": "39926833",
"name": "itsm-workload (1)",
"font_class": "a-itsm-workload1",
"unicode": "e912",
"unicode_decimal": 59666
},
{
"icon_id": "39782649",
"name": "VPC",

Binary file not shown.

View File

@@ -0,0 +1,14 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H2.5V1.25H1.25V2.5H0V1C0 0.447715 0.447715 0 1 0ZM0 7.5V9C0 9.55229 0.447715 10 1 10H2.5V8.75H1.25V7.5H0ZM8.75 7.5V8.75H7.5V10H9C9.55229 10 10 9.55228 10 9V7.5H8.75ZM10 2.5V1C10 0.447715 9.55228 0 9 0H7.5V1.25H8.75V2.5H10Z" fill="url(#paint0_linear_124_16807)"/>
<rect x="2.5" y="3.125" width="5" height="3.75" fill="url(#paint1_linear_124_16807)"/>
<defs>
<linearGradient id="paint0_linear_124_16807" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16807" x1="5" y1="3.125" x2="5" y2="6.875" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 916 B

View File

@@ -0,0 +1,14 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="2.5" width="5" height="5" rx="0.5" fill="url(#paint0_linear_124_16808)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V9C0 9.55229 0.447715 10 1 10H9C9.55229 10 10 9.55228 10 9V1C10 0.447715 9.55228 0 9 0H1ZM8.75 1.25H1.25V8.75H8.75V1.25Z" fill="url(#paint1_linear_124_16808)"/>
<defs>
<linearGradient id="paint0_linear_124_16808" x1="5" y1="2.5" x2="5" y2="7.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16808" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 840 B

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.56845 8.8409C3.06335 8.78963 1.86719 8.05799 2.06279 6.48243C2.1538 5.75105 2.64549 5.3214 3.34457 5.16041C3.67173 5.08909 4.00806 5.06954 4.34128 5.10247C4.40203 5.10811 4.44843 5.11401 4.47689 5.11837L4.51586 5.12631C4.64379 5.15574 4.77263 5.18104 4.90218 5.20219C5.26786 5.2651 5.63914 5.28941 6.0099 5.27474C6.8046 5.23219 7.21015 4.97429 7.23092 4.41672C7.25424 3.79429 6.76332 3.29619 5.86659 2.91832C5.52815 2.77793 5.17843 2.66645 4.82117 2.58506C4.70325 2.55755 4.58482 2.53328 4.46587 2.51226C4.30323 2.94847 3.9867 3.31016 3.57591 3.5292C3.16512 3.74824 2.68841 3.80952 2.23557 3.70149C1.90324 3.61651 1.60053 3.44214 1.36029 3.1973C1.12004 2.95245 0.951447 2.64649 0.872793 2.3126C0.794138 1.97872 0.808429 1.62967 0.914116 1.30333C1.0198 0.976995 1.21285 0.685836 1.4723 0.461451C1.73176 0.237065 2.04771 0.0880244 2.38588 0.0305017C2.72404 -0.0270211 3.07151 0.00917138 3.39056 0.135152C3.70961 0.261132 3.98807 0.472088 4.19571 0.745127C4.40335 1.01817 4.53225 1.34286 4.56841 1.68397C4.6812 1.70269 4.83374 1.73217 5.01524 1.77421C5.42003 1.86601 5.81625 1.99216 6.1996 2.15131C7.38191 2.64966 8.1156 3.39463 8.07638 4.4462C8.03639 5.53187 7.23425 6.04253 6.0563 6.10533C5.62418 6.12373 5.19132 6.09614 4.76503 6.02304C4.61925 5.99997 4.47398 5.9716 4.32923 5.93793C4.30731 5.93532 4.28534 5.9331 4.26335 5.93127C4.02033 5.90687 3.77501 5.92018 3.53606 5.97075C3.15153 6.05893 2.94311 6.24146 2.90056 6.58267C2.78725 7.49504 3.47915 7.94443 5.42694 8.00416C5.44492 7.65558 5.5586 7.3187 5.75548 7.03049C5.95237 6.74229 6.22485 6.51389 6.54303 6.37039C6.8612 6.22689 7.21277 6.17383 7.55912 6.21703C7.90548 6.26023 8.23323 6.39802 8.50641 6.61528C8.77959 6.83254 8.98763 7.12086 9.10769 7.4486C9.22775 7.77634 9.25519 8.13082 9.187 8.47314C9.11881 8.81545 8.95763 9.13235 8.72114 9.38907C8.48465 9.64578 8.18201 9.83237 7.84643 9.92836C7.39921 10.0556 6.92094 10.0153 6.50129 9.81515C6.08164 9.61495 5.74941 9.26855 5.56691 8.8409H5.56845Z" fill="url(#paint0_linear_124_16804)"/>
<defs>
<linearGradient id="paint0_linear_124_16804" x1="5.02318" y1="0.00390625" x2="5.02318" y2="10.0013" gradientUnits="userSpaceOnUse">
<stop stop-color="#497DFF"/>
<stop offset="1" stop-color="#8CD5FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.01211 4.50621L2.7769 5.74077C2.712 5.80565 2.66051 5.88268 2.62538 5.96747C2.59025 6.05225 2.57217 6.14313 2.57217 6.2349C2.57217 6.32668 2.59025 6.41755 2.62538 6.50234C2.66051 6.58712 2.712 6.66416 2.7769 6.72904L3.27085 7.223C3.33573 7.28791 3.41276 7.3394 3.49754 7.37453C3.58232 7.40966 3.67319 7.42774 3.76496 7.42774C3.85674 7.42774 3.94761 7.40966 4.03239 7.37453C4.11717 7.3394 4.1942 7.28791 4.25908 7.223L5.49394 5.98775C5.6237 6.1175 5.72663 6.27155 5.79686 6.44109C5.86708 6.61063 5.90323 6.79234 5.90323 6.97585C5.90323 7.15935 5.86708 7.34106 5.79686 7.5106C5.72663 7.68014 5.6237 7.83419 5.49394 7.96394L3.76479 9.69316C3.56827 9.88963 3.30176 10 3.02387 10C2.74599 10 2.47948 9.88963 2.28296 9.69316L0.306832 7.71696C0.110368 7.52043 0 7.25391 0 6.97602C0 6.69813 0.110368 6.43161 0.306832 6.23508L2.03599 4.50586C2.16574 4.3761 2.31978 4.27317 2.48931 4.20294C2.65884 4.13271 2.84055 4.09657 3.02405 4.09657C3.20755 4.09657 3.38925 4.13271 3.55879 4.20294C3.72832 4.27317 3.88236 4.3761 4.01211 4.50586V4.50621ZM5.98789 5.49414L7.2231 4.25923C7.288 4.19435 7.33949 4.11732 7.37462 4.03253C7.40975 3.94775 7.42783 3.85687 7.42783 3.7651C7.42783 3.67332 7.40975 3.58245 7.37462 3.49766C7.33949 3.41288 7.288 3.33584 7.2231 3.27096L6.72915 2.777C6.66428 2.71209 6.58724 2.6606 6.50246 2.62547C6.41768 2.59034 6.32681 2.57226 6.23504 2.57226C6.14326 2.57226 6.05239 2.59034 5.96761 2.62547C5.88283 2.6606 5.8058 2.71209 5.74092 2.777L4.50606 4.01225C4.3763 3.8825 4.27337 3.72845 4.20314 3.55891C4.13292 3.38937 4.09677 3.20766 4.09677 3.02415C4.09677 2.84065 4.13292 2.65894 4.20314 2.4894C4.27337 2.31986 4.3763 2.16581 4.50606 2.03606L6.23521 0.306843C6.43173 0.110371 6.69824 0 6.97613 0C7.25401 0 7.52052 0.110371 7.71704 0.306843L9.69317 2.28304C9.88963 2.47957 10 2.74609 10 3.02398C10 3.30187 9.88963 3.56839 9.69317 3.76492L7.96401 5.49414C7.83426 5.6239 7.68022 5.72683 7.51069 5.79706C7.34116 5.86729 7.15945 5.90343 6.97595 5.90343C6.79245 5.90343 6.61075 5.86729 6.44121 5.79706C6.27168 5.72683 6.11764 5.6239 5.98789 5.49414ZM3.51817 5.9881L5.98789 3.51829C6.05339 3.45274 6.14225 3.4159 6.23491 3.41586C6.32758 3.41583 6.41646 3.45261 6.48201 3.51812C6.54755 3.58362 6.5844 3.67248 6.58443 3.76515C6.58446 3.85782 6.54768 3.9467 6.48218 4.01225L4.01211 6.48206C3.94661 6.54761 3.85775 6.58445 3.76509 6.58449C3.67242 6.58452 3.58354 6.54774 3.51799 6.48223C3.45245 6.41673 3.4156 6.32787 3.41557 6.2352C3.41554 6.14253 3.45232 6.05365 3.51782 5.9881H3.51817Z" fill="url(#paint0_linear_124_16775)"/>
<defs>
<linearGradient id="paint0_linear_124_16775" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.31822 4.16667H2.54549V2.5C2.54549 1.11458 3.63981 0 5.00003 0C6.36026 0 7.45458 1.11458 7.45458 2.5V4.16667H8.68185C8.90685 4.16667 9.09094 4.35417 9.09094 4.58333V9.58333C9.09094 9.8125 8.90685 10 8.68185 10H1.31822C1.09322 10 0.909124 9.8125 0.909124 9.58333V4.58333C0.909124 4.35417 1.09322 4.16667 1.31822 4.16667ZM5.00003 7.91667C5.45003 7.91667 5.81822 7.54167 5.81822 7.08333C5.81822 6.625 5.45003 6.25 5.00003 6.25C4.55003 6.25 4.18185 6.625 4.18185 7.08333C4.18185 7.54167 4.55003 7.91667 5.00003 7.91667ZM3.36367 4.16667H6.6364V2.5C6.6364 1.58333 5.90003 0.833333 5.00003 0.833333C4.10003 0.833333 3.36367 1.58333 3.36367 2.5V4.16667Z" fill="url(#paint0_linear_124_16805)"/>
<defs>
<linearGradient id="paint0_linear_124_16805" x1="5.00003" y1="0" x2="5.00003" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4D82FF"/>
<stop offset="1" stop-color="#88CFFF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1022 B

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.91242 9.46382C3.91242 9.57428 3.82288 9.66382 3.71242 9.66382H2.35075C2.2403 9.66382 2.15075 9.57428 2.15075 9.46382V3.55962C2.15075 3.44916 2.06121 3.35962 1.95075 3.35962H0.539905C0.354312 3.35962 0.268806 3.12879 0.40961 3.00788L3.58212 0.283626C3.71182 0.172253 3.91242 0.264405 3.91242 0.43536V9.46382ZM6.08758 0.567715C6.08758 0.457258 6.17712 0.367716 6.28758 0.367716H7.64925C7.7597 0.367716 7.84925 0.457259 7.84925 0.567716V6.4411C7.84925 6.55156 7.93879 6.6411 8.04925 6.6411H9.46001C9.64561 6.6411 9.73111 6.87195 9.59029 6.99285L6.41786 9.71645C6.28816 9.8278 6.08758 9.73565 6.08758 9.5647V0.567715Z" fill="url(#paint0_linear_124_16806)"/>
<defs>
<linearGradient id="paint0_linear_124_16806" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 979 B

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.51961 6.8937V10H4.48V6.8937H1.76732C1.65823 6.8937 1.56223 6.85372 1.48369 6.77237C1.40522 6.69504 1.3621 6.5915 1.36369 6.48421C1.36369 5.95891 1.52769 5.48566 1.85895 5.06411C2.18986 4.64428 2.56258 4.43334 2.97893 4.43334V1.64277C2.75966 1.64277 2.57167 1.56142 2.41022 1.39873C2.25355 1.24349 2.16738 1.0362 2.17022 0.821384C2.17022 0.598718 2.25022 0.407762 2.41022 0.244037C2.56912 0.0827244 2.7593 0 2.97893 0H7.01959C7.23885 0 7.42685 0.0813456 7.5883 0.244037C7.74721 0.406728 7.82866 0.598718 7.82866 0.821384C7.82866 1.04405 7.74866 1.23501 7.58867 1.39873C7.4283 1.5628 7.23885 1.64277 7.01959 1.64277V4.43196C7.43594 4.43196 7.81012 4.64291 8.13956 5.06273C8.46631 5.47151 8.64098 5.97137 8.63628 6.48421C8.63628 6.59486 8.59701 6.6924 8.51665 6.77237C8.43665 6.85234 8.34211 6.8937 8.23302 6.8937H5.51998H5.51961Z" fill="url(#paint0_linear_124_16803)"/>
<defs>
<linearGradient id="paint0_linear_124_16803" x1="5.00001" y1="0" x2="5.00001" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -69,6 +69,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.custom-drawer-close {
position: absolute;

View File

@@ -230,6 +230,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.employee-transfer {
width: 100%;
.vue-treeselect__multi-value-item-container {
@@ -262,6 +263,7 @@ export default {
</style>
<style lang="less" scoped>
@import '~@/style/static.less';
.employee-transfer {
display: flex;
justify-content: space-between;

View File

@@ -146,6 +146,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.regex-select {
width: 100%;
height: 300px;

View File

@@ -126,6 +126,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.role-transfer {
display: flex;
justify-content: space-between;

View File

@@ -60,6 +60,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.sidebar-list-item {
.ops_popover_item();
margin: 2px 0;

View File

@@ -52,6 +52,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.two-column-layout {
margin-bottom: -24px;

View File

@@ -4,22 +4,34 @@
@click="jumpTo"
v-if="showTitle && !collapsed"
style="width: 100%; height: 100%; cursor: pointer"
:src="require('@/assets/logo_VECMDB.png')"
:src="file_name ? `/api/common-setting/v1/file/${file_name}` : require('@/assets/logo_VECMDB.png')"
/>
<img
@click="jumpTo"
v-else
style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer"
:src="require('@/assets/logo.png')"
:src="small_file_name ? `/api/common-setting/v1/file/${small_file_name}` : require('@/assets/logo.png')"
/>
<!-- <logo-svg/> -->
<!-- <img v-if="showTitle" style="width:92px;height: 32px" src="@/assets/OneOps.png" /> -->
<!-- <h1 v-if="showTitle">{{ title }}</h1> -->
</div>
</template>
<script>
// import LogoSvg from '@/assets/logo.svg?inline'
import { mapState } from 'vuex'
export default {
name: 'Logo',
components: {},
computed: {},
components: {
// LogoSvg,
},
computed: {
...mapState({
file_name: (state) => state.logo.file_name,
small_file_name: (state) => state.logo.small_file_name,
}),
},
props: {
title: {
type: String,

View File

@@ -96,6 +96,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.color {
color: #custom_colors[color_1];
background-color: #custom_colors[color_2];

View File

@@ -9,11 +9,25 @@
import gridSvg from '@/assets/icons/grid.svg?inline'
import top_agent from '@/assets/icons/top_agent.svg?inline'
import top_acl from '@/assets/icons/top_acl.svg?inline'
import ops_default_show from '@/assets/icons/ops-default_show.svg?inline'
import ops_is_choice from '@/assets/icons/ops-is_choice.svg?inline'
import ops_is_index from '@/assets/icons/ops-is_index.svg?inline'
import ops_is_link from '@/assets/icons/ops-is_link.svg?inline'
import ops_is_password from '@/assets/icons/ops-is_password.svg?inline'
import ops_is_sortable from '@/assets/icons/ops-is_sortable.svg?inline'
import ops_is_unique from '@/assets/icons/ops-is_unique.svg?inline'
import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline'
export {
gridSvg,
top_agent,
top_acl,
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
ops_move_icon
}

View File

@@ -1,14 +0,0 @@
import './highlight.less'
const highlight = (el, binding) => {
if (binding.value.value) {
let testValue = `${binding.value.value}`
if (['(', ')', '$'].includes(testValue)) {
testValue = `\\${testValue}`
}
const regex = new RegExp(`(${testValue})`, 'gi')
el.innerHTML = el.innerText.replace(regex, `<span class='${binding.value.class ?? 'ops-text-highlight'}'>$1</span>`)
}
}
export default highlight

View File

@@ -1,5 +0,0 @@
@import '~@/style/static.less';
.ops-text-highlight {
background-color: @primary-color_3;
}

View File

@@ -1,12 +0,0 @@
import hightlight from './highlight'
const install = function (Vue) {
Vue.directive('hightlight', hightlight)
}
if (window.Vue) {
window.hightlight = hightlight
Vue.use(install); // eslint-disable-line
}
hightlight.install = install
export default hightlight

View File

@@ -1,13 +0,0 @@
import waves from './waves'
const install = function (Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves

View File

@@ -1,26 +0,0 @@
.waves-ripple {
position: absolute;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.15);
background-clip: padding-box;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 1;
}
.waves-ripple.z-active {
opacity: 0;
-webkit-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}

View File

@@ -1,72 +0,0 @@
import './waves.css'
const context = '@@wavesContext'
function handleClick(el, binding) {
function handle(e) {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit 点击位置扩散 center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
},
customOpts
)
const target = opts.ele
if (target) {
target.style.position = 'relative'
target.style.overflow = 'hidden'
const rect = target.getBoundingClientRect()
let ripple = target.querySelector('.waves-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'waves-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'waves-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
break
default:
ripple.style.top =
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
document.body.scrollTop) + 'px'
ripple.style.left =
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.color
ripple.className = 'waves-ripple z-active'
return false
}
}
if (!el[context]) {
el[context] = {
removeHandle: handle
}
} else {
el[context].removeHandle = handle
}
return handle
}
export default {
bind(el, binding) {
el.addEventListener('click', handleClick(el, binding), false)
},
update(el, binding) {
el.removeEventListener('click', el[context].removeHandle, false)
el.addEventListener('click', handleClick(el, binding), false)
},
unbind(el) {
el.removeEventListener('click', el[context].removeHandle, false)
el[context] = null
delete el[context]
}
}

View File

@@ -31,6 +31,7 @@ router.beforeEach(async (to, from, next) => {
store.dispatch("loadAllUsers")
store.dispatch("loadAllEmployees")
store.dispatch("loadAllDepartments")
store.dispatch("getCompanyInfo")
store.dispatch('GenerateRoutes', { roles }).then(() => {
router.addRoutes(store.getters.appRoutes)
const redirect = decodeURIComponent(from.query.redirect || to.path)

View File

@@ -233,6 +233,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.acl-history {
border-radius: @border-radius-box;

View File

@@ -32,6 +32,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.acl-operation-history {
border-radius: @border-radius-box;

View File

@@ -189,6 +189,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.acl-resource-types {
border-radius: @border-radius-box;

View File

@@ -352,6 +352,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.acl-resources {
border-radius: @border-radius-box;

View File

@@ -285,6 +285,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.acl-roles {
border-radius: @border-radius-box;

View File

@@ -88,6 +88,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.acl-secret-key {
background-color: #fff;

View File

@@ -320,6 +320,7 @@ export default {
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.acl-trigger {
border-radius: @border-radius-box;

View File

@@ -188,6 +188,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.acl-users {
border-radius: @border-radius-box;

View File

@@ -73,11 +73,3 @@ export function deleteCIRelationView(firstCiId, secondCiId, data) {
data
})
}
export function searchCIRelationFull(params) {
return axios({
url: `/v0.1/ci_relations/search/full`,
method: 'GET',
params,
})
}

View File

@@ -221,6 +221,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.cmdb-transfer {
.ant-transfer-list {

View File

@@ -311,6 +311,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-grant {
position: relative;
padding: 0 20px;
@@ -323,6 +324,7 @@ export default {
</style>
<style lang="less">
@import '~@/style/static.less';
.cmdb-grant {
.grant-button {

View File

@@ -59,6 +59,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.read-checkbox {
.read-checkbox-half-checked {
width: 16px;

View File

@@ -112,6 +112,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.notice-content {
width: 100%;
& &-main {
@@ -185,6 +186,7 @@ export default {
</style>
<style lang="less">
@import '~@/style/static.less';
.notice-content {
.w-e-bar {

View File

@@ -95,12 +95,12 @@
isFocusExpression = false
}
"
:class="{ 'ci-searchform-expression': true, 'ci-searchform-expression-has-value': expression }"
class="ci-searchform-expression"
:style="{ width }"
:placeholder="placeholder"
@keyup.enter="emitRefresh"
>
<a-icon slot="suffix" type="check-circle" @click="handleCopyExpression" />
<ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" />
</a-input>
<slot></slot>
</a-space>
@@ -264,6 +264,7 @@ export default {
}
</script>
<style lang="less">
@import '~@/style/static.less';
@import '../../views/index.less';
.ci-searchform-expression {
> input {
@@ -284,9 +285,6 @@ export default {
cursor: pointer;
}
}
.ci-searchform-expression-has-value .ant-input-suffix {
color: @func-color_3;
}
.cmdb-search-form {
.ant-form-item-label {
overflow: hidden;
@@ -297,6 +295,8 @@ export default {
</style>
<style lang="less" scoped>
@import '~@/style/static.less';
.search-form-bar {
margin-bottom: 20px;
display: flex;

View File

@@ -244,6 +244,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-subscribe-drawer {
.cmdb-subscribe-drawer-container {
@@ -314,6 +315,7 @@ export default {
</style>
<style lang="less">
@import '~@/style/static.less';
.cmdb-subscribe-drawer {
.ant-tabs-bar {
background-color: #custom_colors[color_2];

View File

@@ -194,10 +194,7 @@ const cmdb_en = {
attributeAssociationTip3: 'Two Attributes must be selected',
attributeAssociationTip4: 'Please select a attribute from Source CIType',
attributeAssociationTip5: 'Please select a attribute from Target CIType',
show: 'show attribute',
setAsShow: 'Set as show attribute',
cancelSetAsShow: 'Cancel show attribute',
showTips: 'The names of nodes in the service tree and topology view'
},
components: {
unselectAttributes: 'Unselected',
@@ -533,8 +530,7 @@ if __name__ == "__main__":
peopleHasRead: 'Personnel authorized to read:',
authorizationPolicy: 'CI Authorization Policy:',
idAuthorizationPolicy: 'Authorized by node:',
view: 'View permissions',
searchTips: 'Search in service tree'
view: 'View permissions'
},
tree: {
tips1: 'Please go to Preference page first to complete your subscription!',

View File

@@ -194,10 +194,6 @@ const cmdb_zh = {
attributeAssociationTip3: '属性关联必须选择两个属性',
attributeAssociationTip4: '请选择原模型属性',
attributeAssociationTip5: '请选择目标模型属性',
show: '展示属性',
setAsShow: '设置为展示属性',
cancelSetAsShow: '取消设置为展示属性',
showTips: '服务树和拓扑视图里节点的名称'
},
components: {
unselectAttributes: '未选属性',
@@ -533,8 +529,7 @@ if __name__ == "__main__":
peopleHasRead: '当前有查看权限的人员:',
authorizationPolicy: '实例授权策略:',
idAuthorizationPolicy: '按节点授权的:',
view: '查看权限',
searchTips: '在服务树中筛选'
view: '查看权限'
},
tree: {
tips1: '请先到 我的订阅 页面完成订阅!',

View File

@@ -147,6 +147,7 @@ export default {
}
</script>
<style lang="less">
@import '~@/style/static.less';
@import '../index.less';
.cmdb-batch-upload-label {
color: @text-color_1;
@@ -158,6 +159,7 @@ export default {
}
</style>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-batch-upload {
margin-bottom: -24px;

View File

@@ -113,6 +113,7 @@ export default {
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-batch-upload-table {
height: 200px;

View File

@@ -83,6 +83,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.cmdb-batch-upload-dragger {
height: auto;
@@ -111,6 +112,7 @@ export default {
}
</style>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-batch-upload-dragger {
position: relative;

View File

@@ -101,6 +101,7 @@ export default {
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-batch-upload-result {
.cmdb-batch-upload-result-content {

View File

@@ -55,6 +55,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.ci-detail-header {
border-left: 3px solid @primary-color;

View File

@@ -992,6 +992,7 @@ export default {
</style>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-ci {
background-color: #fff;
padding: 20px;

View File

@@ -150,11 +150,11 @@ export default {
computed: {
topoData() {
const ci_types_list = this.ci_types()
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
const unique_id = _findCiType.show_id || this.attributes().unique_id
const unique_name = _findCiType.show_name || this.attributes().unique
const unique_id = this.attributes().unique_id
const unique_name = this.attributes().unique
const _findUnique = this.attrList().find((attr) => attr.id === unique_id)
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
const nodes = {
isRoot: true,
id: `Root_${this.typeId}`,
@@ -183,10 +183,6 @@ export default {
this.parentCITypes.forEach((parent) => {
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
if (this.firstCIs[parent.name]) {
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = parent.attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
this.firstCIs[parent.name].forEach((parentCi) => {
nodes.children.push({
id: `${parentCi._id}`,
@@ -194,9 +190,9 @@ export default {
title: parent.alias || parent.name,
name: parent.name,
side: 'left',
unique_alias,
unique_name,
unique_value: parentCi[unique_name],
unique_alias: parentCi.unique_alias,
unique_name: parentCi.unique,
unique_value: parentCi[parentCi.unique],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
@@ -226,10 +222,6 @@ export default {
this.childCITypes.forEach((child) => {
const _findCiType = ci_types_list.find((item) => item.id === child.id)
if (this.secondCIs[child.name]) {
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = child.attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
this.secondCIs[child.name].forEach((childCi) => {
nodes.children.push({
id: `${childCi._id}`,
@@ -237,9 +229,9 @@ export default {
title: child.alias || child.name,
name: child.name,
side: 'right',
unique_alias,
unique_name,
unique_value: childCi[unique_name],
unique_alias: childCi.unique_alias,
unique_name: childCi.unique,
unique_value: childCi[childCi.unique],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
@@ -268,24 +260,6 @@ export default {
})
return { nodes, edges }
},
exsited_ci() {
const _exsited_ci = [this.typeId]
this.parentCITypes.forEach((parent) => {
if (this.firstCIs[parent.name]) {
this.firstCIs[parent.name].forEach((parentCi) => {
_exsited_ci.push(parentCi._id)
})
}
})
this.childCITypes.forEach((child) => {
if (this.secondCIs[child.name]) {
this.secondCIs[child.name].forEach((childCi) => {
_exsited_ci.push(childCi._id)
})
}
})
return _exsited_ci
},
},
inject: {
attrList: { from: 'attrList' },
@@ -304,7 +278,6 @@ export default {
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
if (isFirst && this.$refs.ciDetailRelationTopo) {
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
}
})
@@ -341,6 +314,7 @@ export default {
secondCIs[item.ci_type] = [item]
}
})
console.log(_.cloneDeep(secondCIs))
this.secondCIs = secondCIs
})
.catch((e) => {})
@@ -440,7 +414,6 @@ export default {
handleChangeActiveKey(e) {
if (e.target.value === '1') {
this.$nextTick(() => {
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
})
}

View File

@@ -15,7 +15,7 @@
position: absolute;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 2px;
border-radius: 10px;
padding: 4px 8px;
width: 100px;
text-align: center;
@@ -74,11 +74,13 @@
}
.root {
width: 100px;
border-color: @primary-color;
font-weight: 700;
background: #2f54eb;
border: none;
border-radius: 5px;
font-weight: 500;
padding: 4px 8px;
.title {
color: @primary-color;
color: #fff;
}
}
}

View File

@@ -10,7 +10,6 @@
import _ from 'lodash'
import { TreeCanvas } from 'butterfly-dag'
import { searchCIRelation } from '@/modules/cmdb/api/CIRelation'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import Node from './node.js'
import 'butterfly-dag/dist/index.css'
@@ -88,7 +87,7 @@ export default {
this.canvas.focusCenterWithAnimate()
})
},
async redrawData(res, sourceNode, side) {
redrawData(res, sourceNode, side) {
const newNodes = []
const newEdges = []
if (!res.result.length) {
@@ -96,24 +95,18 @@ export default {
return
}
const ci_types_list = this.ci_types()
for (let i = 0; i < res.result.length; i++) {
const r = res.result[i]
res.result.forEach((r) => {
if (!this.exsited_ci.includes(r._id)) {
const _findCiType = ci_types_list.find((item) => item.id === r._type)
const { attributes } = await getCITypeAttributesById(_findCiType.id)
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
newNodes.push({
id: `${r._id}`,
Class: Node,
title: r.ci_type_alias || r.ci_type,
name: r.ci_type,
side: side,
unique_alias,
unique_name,
unique_value: r[unique_name],
unique_alias: r.unique_alias,
unique_name: r.unique,
unique_value: r[r.unique],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
@@ -138,8 +131,7 @@ export default {
targetNode: side === 'right' ? `${r._id}` : sourceNode,
type: 'endpoint',
})
}
})
const { nodes, edges } = this.canvas.getDataMap()
// 删除原节点和边
this.canvas.removeNodes(nodes.map((node) => node.id))

View File

@@ -204,6 +204,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
.attr-ad {
position: relative;
padding: 0 20px;

View File

@@ -10,7 +10,6 @@
:class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }"
>
<div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div>
<div class="attribute-card-uniqueKey" v-if="isShowId">{{ $t('cmdb.ciType.show') }}</div>
<template v-if="!isAdd">
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
<div class="attribute-card-content">
@@ -28,7 +27,6 @@
<div
class="attribute-card-trigger"
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
:style="{ top: isShowId ? '18px' : '' }"
>
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
</div>
@@ -66,24 +64,12 @@
</a-space>
</a-popover>
<a-space class="attribute-card-operation">
<a v-if="!isStore && !inherited"><a-icon type="edit" @click="handleEdit"/></a>
<a-tooltip
v-if="
!isStore &&
!isUnique &&
!['6'].includes(property.value_type) &&
!property.is_password &&
!property.is_list
"
:title="$t(isShowId ? 'cmdb.ciType.cancelSetAsShow' : 'cmdb.ciType.setAsShow')"
>
<a><ops-icon type="veops-show" @click="setAsShow"/></a>
<a-space class="attribute-card-operation" v-if="!inherited">
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
</a-tooltip>
<a-tooltip v-if="!isStore && property.is_computed" :title="$t('cmdb.ciType.computeForAllCITips')">
<a><a-icon type="redo" @click="handleCalcComputed"/></a>
</a-tooltip>
<a v-if="!isUnique && !inherited" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
<a v-if="!isUnique" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
</a-space>
</div>
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
@@ -98,9 +84,17 @@
<script>
import { deleteCITypeAttributesById, deleteAttributesById, calcComputedAttribute } from '@/modules/cmdb/api/CITypeAttr'
import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon'
import {
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
} from '@/core/icons'
import { valueTypeMap } from '../../utils/const'
import TriggerForm from './triggerForm.vue'
import { updateCIType } from '@/modules/cmdb/api/CIType'
export default {
name: 'AttributeCard',
inject: {
@@ -108,14 +102,17 @@ export default {
from: 'unique',
default: () => undefined,
},
show_id: {
from: 'show_id',
default: () => undefined,
},
},
components: {
ValueTypeIcon,
TriggerForm,
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
},
props: {
property: {
@@ -149,12 +146,6 @@ export default {
}
return false
},
isShowId() {
if (this.show_id) {
return this.property?.id === this.show_id()
}
return false
},
valueTypeMap() {
return valueTypeMap()
},
@@ -226,16 +217,12 @@ export default {
},
})
},
setAsShow() {
updateCIType(this.CITypeId, { show_id: this.isShowId ? null : this.property?.id }).then((res) => {
this.$emit('ok')
})
},
},
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.attribute-card {
width: 182px;
height: 80px;

View File

@@ -164,6 +164,7 @@ export default {
</style>
<style lang="less">
@import '~@/style/static.less';
.attrbute-store-search {
width: 300px;

View File

@@ -186,7 +186,6 @@ import {
createCITypeGroupById,
updateCITypeGroupById,
getTriggerList,
getCIType,
} from '@/modules/cmdb/api/CIType'
import {
getCITypeAttributesById,
@@ -232,7 +231,6 @@ export default {
newGroupName: '',
attrTypeFilter: [],
unique: '',
show_id: null,
}
},
computed: {
@@ -252,31 +250,20 @@ export default {
unique: () => {
return this.unique
},
show_id: () => {
return this.show_id
},
}
},
beforeCreate() {},
created() {},
mounted() {
this.init()
this.getCITypeGroupData()
},
methods: {
getPropertyIcon,
init() {
getCIType(this.CITypeId).then((res) => {
if (res?.ci_types && res.ci_types.length) {
this.show_id = res.ci_types[0]?.show_id ?? null
}
})
this.getCITypeGroupData()
},
handleEditProperty(property) {
this.$refs.attributeEditForm.handleEdit(property, this.attributes)
},
handleOk() {
this.init()
this.getCITypeGroupData()
},
setOtherGroupAttributes() {
const orderMap = this.attributes.reduce(function(map, obj) {
@@ -604,6 +591,8 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.fold {
width: calc(100% - 216px);
display: inline-block;

View File

@@ -77,6 +77,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.citype-detail-title {
border-left: 4px solid @primary-color;

View File

@@ -53,10 +53,11 @@
</a-menu-item>
<a-menu-item key="1">
<a-space>
<a href="/api/v0.1/ci_types/template/export/file">
<a-icon type="download" /> {{ $t('download') }}
</a></a-space
>
<a
href="/api/v0.1/ci_types/template/export/file"
><a-icon type="download" /> {{ $t('download') }}</a
>
</a-space>
</a-menu-item>
</a-menu>
</a-dropdown>
@@ -261,7 +262,6 @@
'default_order_attr',
{ rules: [{ required: false, message: $t('cmdb.ciType.selectDefaultOrderAttr') }] },
]"
:placeholder="$t('placeholder2')"
>
<el-option
:key="item.name"
@@ -299,7 +299,6 @@
filterInput = ''
}
"
@change="handleChangeUnique"
>
<el-option
:key="item.id"
@@ -314,40 +313,6 @@
<a-divider type="vertical" />
<a @click="handleCreatNewAttr">{{ $t('cmdb.ciType.notfound') }}</a>
</a-form-item>
<a-form-item
:help="$t('cmdb.ciType.showTips')"
:label="$t('cmdb.ciType.show')"
v-if="drawerTitle === $t('cmdb.ciType.editCIType')"
>
<el-select
size="small"
filterable
clearable
name="show_id"
:filter-method="
(input) => {
showIdFilterInput = input
}
"
v-decorator="['show_id', { rules: [{ required: false }] }]"
:placeholder="$t('placeholder2')"
@visible-change="
() => {
showIdFilterInput = ''
}
"
>
<el-option
:key="item.id"
:value="item.id"
v-for="item in showIdSelectOptions"
:label="item.alias || item.name"
>
<span> {{ item.alias || item.name }}</span>
<span :title="item.name" style="font-size: 10px; color: #afafaf"> {{ item.name }}</span>
</el-option>
</el-select>
</a-form-item>
<div v-if="newAttrAreaVisible" :style="{ padding: '15px 8px 0 8px', backgroundColor: '#fafafa' }">
<create-new-attribute
ref="createNewAttribute"
@@ -453,17 +418,14 @@ export default {
addId: null,
filterInput: '',
showIdFilterInput: '',
currentTypeAttrs: [],
orderSelectionOptions: [],
default_order_asc: '1',
allTreeDepAndEmp: [],
editCiType: null,
isInherit: false,
unique_id: null,
}
},
computed: {
@@ -520,22 +482,6 @@ export default {
}
return _attributes
},
orderSelectionOptions() {
return this.currentTypeAttrs.filter((item) => item.is_required)
},
showIdSelectOptions() {
const _showIdSelectOptions = this.currentTypeAttrs.filter(
(item) => item.id !== this.unique_id && !['6'].includes(item.value_type) && !item.is_password && !item.is_list
)
if (this.showIdFilterInput) {
return _showIdSelectOptions.filter(
(item) =>
item.name.toLowerCase().includes(this.showIdFilterInput.toLowerCase()) ||
item.alias.toLowerCase().includes(this.showIdFilterInput.toLowerCase())
)
}
return _showIdSelectOptions
},
},
provide() {
return {
@@ -713,7 +659,6 @@ export default {
delete values.parent_ids
await this.updateCIType(values.id, {
...values,
show_id: values.show_id || null,
icon,
})
} else {
@@ -919,8 +864,7 @@ export default {
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
this.drawerVisible = true
await getCITypeAttributesById(record.id).then((res) => {
this.currentTypeAttrs = res.attributes
this.unique_id = res.unique_id
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required)
})
await getCIType(record.id).then((res) => {
const ci_type = res.ci_types[0]
@@ -933,9 +877,6 @@ export default {
})
})
}
this.form.setFieldsValue({
show_id: ci_type.show_id ?? null,
})
})
this.$nextTick(() => {
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
@@ -1000,18 +941,13 @@ export default {
this.$message.error({ content: this.$t('cmdb.ciType.uploadFailed'), key, duration: 2 })
}
},
handleChangeUnique(value) {
this.unique_id = value
const show_id = this.form.getFieldValue('show_id')
if (show_id === value) {
this.form.setFieldsValue({ show_id: '' })
}
},
},
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.ci-types-wrap {
margin: 0 0 -24px 0;
.ci-types-empty {

View File

@@ -634,6 +634,7 @@ export default {
</style>
<style lang="less" scoped>
@import '~@/style/static.less';
.auto-complete-wrapper {
position: relative;

View File

@@ -142,6 +142,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.setting-discovery {
background-color: #fff;
padding: 20px;

View File

@@ -313,6 +313,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-adc {
.cmdb-adc-group {
margin-bottom: 20px;

View File

@@ -308,6 +308,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.model-relation {
background-color: #fff;

View File

@@ -393,6 +393,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-preference {
margin: -24px;
overflow: auto;

View File

@@ -11,14 +11,6 @@
<template #one>
<div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }">
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
<a-input
:placeholder="$t('cmdb.serviceTree.searchTips')"
class="relation-views-left-input"
@pressEnter="handleSearchFull"
v-model="fullSearchValue"
>
<a-icon slot="prefix" type="search" />
</a-input>
<div
class="ops-list-batch-action"
:style="{ marginBottom: '10px' }"
@@ -55,7 +47,6 @@
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
</div>
<a-tree
v-if="!isFullSearch"
:selectedKeys="selectedKeys"
:loadData="onLoadData"
:treeData="treeData"
@@ -64,10 +55,13 @@
@drop="onDrop"
:expandedKeys="expandedKeys"
>
<template #title="treeNodeData">
<template #title="{ key: treeKey, title,number, isLeaf }">
<ContextMenu
:treeNodeData="treeNodeData"
:title="title"
:number="number"
:treeKey="treeKey"
:levels="levels"
:isLeaf="isLeaf"
:currentViews="relationViews.views[viewName]"
:id2type="relationViews.id2type"
@onContextMenuClick="onContextMenuClick"
@@ -80,30 +74,6 @@
/>
</template>
</a-tree>
<a-tree
v-else
:treeData="filterFullTreeData"
defaultExpandAll
:selectedKeys="selectedKeys"
:expandedKeys="expandedKeys"
>
<template #title="treeNodeData">
<ContextMenu
:treeNodeData="treeNodeData"
:levels="levels"
:currentViews="relationViews.views[viewName]"
:id2type="relationViews.id2type"
@onContextMenuClick="onContextMenuClick"
@onNodeClick="onNodeClick"
:ciTypeIcons="ciTypeIcons"
:showBatchLevel="showBatchLevel"
:batchTreeKey="batchTreeKey"
@clickCheckbox="clickCheckbox"
@updateTreeData="updateTreeData"
:fullSearchValue="fullSearchValue"
/>
</template>
</a-tree>
</div>
</template>
<template #two>
@@ -372,6 +342,7 @@
total,
})
"
:style="{ alignSelf: 'flex-end', marginRight: '12px' }"
>
<template slot="buildOptionText" slot-scope="props">
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('cmdb.history.itemsPerPage') }}</span>
@@ -421,7 +392,6 @@ import {
batchDeleteCIRelation,
batchUpdateCIRelationChildren,
addCIRelationView,
searchCIRelationFull,
} from '@/modules/cmdb/api/CIRelation'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
@@ -519,13 +489,9 @@ export default {
statisticsObj: {},
viewOption: {},
loadRootStatisticsParams: {},
fullSearchValue: '',
isFullSearch: false,
fullTreeData: [],
filterFullTreeData: [],
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
@@ -534,6 +500,9 @@ export default {
return this.windowHeight - 244
},
selectedKeys() {
if (this.treeKeys.length <= 1) {
return this.treeKeys.map((item) => `@^@${item}`)
}
return [this.treeKeys.join('@^@')]
},
isLeaf() {
@@ -607,7 +576,7 @@ export default {
this.reload()
},
pageNo: function(newPage, oldPage) {
this.loadData({ params: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable })
this.loadData({ pageNo: newPage }, undefined, this.sortByTable)
},
},
@@ -635,7 +604,7 @@ export default {
this.loadData()
},
async loadData({ parameter, refreshType = undefined, sortByTable = undefined } = {}) {
async loadData(parameter, refreshType = undefined, sortByTable = undefined) {
// refreshType='refreshNumber' 树图只更新number
const params = Object.assign(parameter || {}, (this.$refs.search || {}).queryParam)
let q = ''
@@ -655,6 +624,8 @@ export default {
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
if (exp) {
// exp = exp.replace(/(\:)/g, '$1*')
// exp = exp.replace(/(\,)/g, '*$1')
q = `${q},${exp}`
}
@@ -685,9 +656,38 @@ export default {
}
if (this.treeKeys.length === 0) {
if (!refreshType && !this.isFullSearch) {
// await this.judgeCITypes(q)
if (!refreshType) {
await this.loadRoot()
}
// const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
// if (fuzzySearch) {
// q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
// } else {
// q = `q=_type:${this.currentTypeId[0]},` + q
// }
// if (this.currentTypeId[0] && this.treeData && this.treeData.length) {
// // default select first node
// this.onNodeClick(this.treeData[0].key)
// const res = await searchCI2(q)
// const root_id = this.treeData.map((item) => item.id).join(',')
// q += `&root_id=${root_id}`
// this.pageNo = res.page
// this.numfound = res.numfound
// res.result.forEach((item, index) => (item.key = item._id))
// const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
// console.log(jsonAttrList)
// this.instanceList = res['result'].map((item) => {
// jsonAttrList.forEach(
// (jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
// )
// return { ..._.cloneDeep(item) }
// })
// this.initialInstanceList = _.cloneDeep(this.instanceList)
// this.calcColumns()
// }
} else {
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
@@ -726,7 +726,7 @@ export default {
}
q += `&level=${this.topo_flatten.includes(this.currentTypeId[0]) ? 1 : level.join(',')}`
if (!refreshType && !this.isFullSearch) {
if (!refreshType) {
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
}
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
@@ -812,6 +812,9 @@ export default {
this.selectedRowKeys = []
this.currentTypeId = [typeId]
this.loadColumns()
// this.$nextTick(() => {
// this.refreshTable()
// })
},
async judgeCITypes() {
@@ -851,6 +854,64 @@ export default {
this.currentTypeId = [this.showTypeIds[0]]
await this.loadColumns()
}
// const showTypeIds = []
// let _showTypes = []
// let _showTypeIds = []
// if (this.treeKeys.length) {
// const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
// _showTypes = this.node2ShowTypes[typeId + '']
// _showTypes.forEach((item) => {
// _showTypeIds.push(item.id)
// })
// } else {
// _showTypeIds = JSON.parse(JSON.stringify(this.origShowTypeIds))
// _showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
// }
// const promises = _showTypeIds.map((typeId) => {
// let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
// if (Object.values(this.level2constraint).includes('2')) {
// _q = _q + `&has_m2m=1`
// }
// if (this.root_parent_path) {
// _q = _q + `&root_parent_path=${this.root_parent_path}`
// }
// // if (this.treeKeys.length === 0) {
// // return searchCI2(_q).then((res) => {
// // if (res.numfound !== 0) {
// // showTypeIds.push(typeId)
// // }
// // })
// // } else {
// _q = _q + `&descendant_ids=${this.descendant_ids}`
// return searchCIRelation(_q).then((res) => {
// if (res.numfound !== 0) {
// showTypeIds.push(typeId)
// }
// })
// // }
// })
// await Promise.all(promises).then(async () => {
// if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
// const showTypes = []
// _showTypes.forEach((item) => {
// if (showTypeIds.includes(item.id)) {
// showTypes.push(item)
// }
// })
// console.log(showTypes)
// this.showTypes = showTypes
// this.showTypeIds = showTypeIds
// if (
// !this.currentTypeId.length ||
// (this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
// ) {
// this.currentTypeId = [this.showTypeIds[0]]
// await this.loadColumns()
// }
// }
// })
},
async loadRoot() {
@@ -858,41 +919,29 @@ export default {
const facet = []
const ciIds = []
res.result.forEach((item) => {
const showName = this.relationViews.id2type[item._type]?.show_name ?? null
facet.push({
showName,
showNameValue: item[showName] ?? null,
uniqueValue: item[item.unique],
number: 0,
ciId: item._id,
typeId: item._type,
unique: item.unique,
})
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
ciIds.push(item._id)
})
const leafId = this.leaf[0]
let level = 0
this.levels.forEach((item, idx) => {
if (item.includes(leafId)) {
level = idx + 1
}
})
const params = {
level,
root_ids: ciIds.join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
}
this.loadRootStatisticsParams = params
await statisticsCIRelation({
...params,
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => {
facet.forEach((item, idx) => {
item.number += num[ciIds[idx] + '']
const promises = this.leaf.map((leafId) => {
let level = 0
this.levels.forEach((item, idx) => {
if (item.includes(leafId)) {
level = idx + 1
}
})
return statisticsCIRelation({
root_ids: ciIds.join(','),
level: level,
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => {
facet.forEach((item, idx) => {
item[1] += num[ciIds[idx] + '']
})
})
})
await Promise.all(promises)
this.wrapTreeData(facet)
// default select first node
this.onNodeClick(this.treeData[0].key)
@@ -927,16 +976,7 @@ export default {
const facet = []
const ciIds = []
res.result.forEach((item) => {
const showName = this.relationViews.id2type[item._type]?.show_name ?? null
facet.push({
showName,
showNameValue: item[showName] ?? null,
uniqueValue: item[item.unique],
number: 0,
ciId: item._id,
typeId: item._type,
unique: item.unique,
})
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
ciIds.push(item._id)
})
let ancestor_ids
@@ -958,7 +998,7 @@ export default {
descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => {
facet.forEach((item, idx) => {
item.number += num[ciIds[idx] + '']
item[1] += num[ciIds[idx] + '']
})
})
}
@@ -969,7 +1009,7 @@ export default {
}
},
onNodeClick(keys, callback = undefined) {
onNodeClick(keys) {
this.triggerSelect = true
if (keys) {
const _tempKeys = keys.split('@^@').filter((item) => item !== '')
@@ -988,9 +1028,6 @@ export default {
}
this.refreshTable()
if (callback && typeof callback === 'function') {
callback()
}
},
wrapTreeData(facet) {
if (this.triggerSelect) {
@@ -998,15 +1035,12 @@ export default {
}
const treeData = []
facet.forEach((item) => {
const _treeKeys = _.cloneDeep(this.treeKeys)
_treeKeys.push(item.ciId + '%' + item.typeId + '%' + `{"${item.unique}":"${item.uniqueValue}"}`)
treeData.push({
title: item.showName ? item.showNameValue : item.uniqueValue,
number: item.number,
key: _treeKeys.join('@^@'),
isLeaf: this.leaf.includes(item.typeId),
id: item.ciId,
showName: item.showName,
title: item[0],
number: item[1],
key: this.treeKeys.join('@^@') + '@^@' + item[2] + '%' + item[3] + '%' + `{"${item[4]}":"${item[0]}"}`,
isLeaf: this.leaf.includes(item[3]),
id: item[2],
})
})
if (this.treeNode === null) {
@@ -1026,6 +1060,7 @@ export default {
}
this.treeKeys = treeNode.eventKey.split('@^@').filter((item) => item !== '')
this.treeNode = treeNode
// this.refreshTable()
resolve()
})
},
@@ -1193,7 +1228,7 @@ export default {
that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve()
that.selectedRowKeys = []
that.loadData({ params: {}, refreshType: 'refreshNumber' })
that.loadData({}, 'refreshNumber')
})
},
})
@@ -1206,7 +1241,9 @@ export default {
const _splitTargetKey = targetKey.split('@^@').filter((item) => item !== '')
if (_splitDragKey.length - 1 === _splitTargetKey.length) {
const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0]
// const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2])
const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0]
// TODO 拖拽这里不造咋弄 等等再说吧
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
this.reload()
})
@@ -1241,7 +1278,7 @@ export default {
this.sortByTable = sortByTable
this.$nextTick(() => {
if (this.pageNo === 1) {
this.loadData({ params: {}, refreshType: undefined, sortByTable })
this.loadData({}, undefined, sortByTable)
} else {
this.pageNo = 1
}
@@ -1400,7 +1437,7 @@ export default {
onOk() {
deleteCI(record.ci_id || record._id).then((res) => {
that.$message.success(that.$t('deleteSuccess'))
that.loadData({ params: {}, refreshType: 'refreshNumber' })
that.loadData({}, 'refreshNumber')
})
},
})
@@ -1420,7 +1457,7 @@ export default {
}
addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => {
setTimeout(() => {
this.loadData({ params: {}, refreshType: 'refreshNumber' })
this.loadData({}, 'refreshNumber')
}, 500)
})
},
@@ -1458,7 +1495,7 @@ export default {
.finally(() => {
that.loading = false
setTimeout(() => {
that.loadData({ params: {} })
that.loadData({})
}, 800)
})
},
@@ -1521,7 +1558,7 @@ export default {
that.selectedRowKeys = []
that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve()
that.loadData({ params: {}, refreshType: 'refreshNumber' })
that.loadData({}, 'refreshNumber')
})
},
})
@@ -1531,6 +1568,7 @@ export default {
},
jsonEditorOk(row, column, jsonData) {
// 后端写数据有快慢不拉接口直接修改table的数据
// this.reloadData()
this.instanceList.forEach((item) => {
if (item._id === row._id) {
item[column.property] = JSON.stringify(jsonData)
@@ -1539,7 +1577,7 @@ export default {
this.$refs.xTable.refreshColumn()
},
relationViewRefreshNumber() {
this.loadData({ params: {}, refreshType: 'refreshNumber' })
this.loadData({}, 'refreshNumber')
},
onShowSizeChange(current, pageSize) {
this.pageSize = pageSize
@@ -1716,9 +1754,11 @@ export default {
return node[i]
}
if (node[i].children && node[i].children.length) {
const found = this.findNode(node[i].children, target)
if (found) {
return found
for (let i = 0; i < node[i].children.length; i++) {
const found = this.findNode(node[i].children, target)
if (found) {
return found
}
}
}
}
@@ -1731,85 +1771,13 @@ export default {
}
this.refreshTable()
},
handleSearchFull(e) {
const value = e.target.value
this.treeKeys = []
this.expandedKeys = []
if (!value) {
this.reload()
return
}
if (this.isFullSearch) {
this.calcFilterFullTreeData()
return
}
searchCIRelationFull({
...this.loadRootStatisticsParams,
type_ids: this.topo_flatten.join(','),
}).then((res) => {
this.isFullSearch = true
this.fullTreeData = this.formatTreeData(res)
this.calcFilterFullTreeData()
})
},
calcFilterFullTreeData() {
const _expandedKeys = []
const predicateCiIds = []
const filterTree = (node, predicate) => {
if (predicate(node)) {
predicateCiIds.push(node.id)
return true
}
if (node.children) {
node.children = node.children.filter((child) => {
if (predicateCiIds.some((id) => child.key.includes(String(id)))) {
return true
}
return filterTree(child, predicate)
})
if (node.children.length && !predicateCiIds.some((id) => node.key.includes(String(id)))) {
_expandedKeys.push(node.key)
}
return node.children.length > 0
}
return false
}
const predicate = (node) =>
String(node.title)
.toLowerCase()
.includes(this.fullSearchValue.toLowerCase())
const _fullTreeData = _.cloneDeep(this.fullTreeData)
this.filterFullTreeData = _fullTreeData.filter((item) => filterTree(item, predicate))
if (this.filterFullTreeData && this.filterFullTreeData.length) {
this.onNodeClick(this.filterFullTreeData[0].key, () => {
this.expandedKeys = _expandedKeys
})
} else {
this.treeKeys = []
this.instanceList = []
}
},
formatTreeData(array, parentKey = '') {
array.forEach((item) => {
const showName = this.relationViews.id2type[item.type_id]?.show_name ?? null
const uniqueName = this.relationViews.id2type[item.type_id]?.unique_name ?? null
const keyList = parentKey.split('@^@').filter((item) => !!item)
keyList.push(item.id + '%' + item.type_id + '%' + `{"${uniqueName}":"${item.uniqueValue}"}`)
const key = keyList.join('@^@')
item.key = key
item.showName = showName
if (!item.isLeaf && item.children && item.children.length) {
item.children = this.formatTreeData(item.children, key)
}
})
return array
},
},
}
</script>
<style lang="less">
@import '../index.less';
@import '~@/style/static.less';
.relation-views-wrapper {
width: 100%;
@@ -1852,18 +1820,6 @@ export default {
padding: 0 6px;
}
}
.relation-views-left-input {
margin-bottom: 12px;
input {
background-color: transparent;
border-top: none;
border-right: none;
border-left: none;
}
.ant-input:focus {
box-shadow: none;
}
}
}
.relation-views-right {
width: 100%;

View File

@@ -27,13 +27,7 @@
/>
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
</template>
<span
class="relation-views-node-title"
v-if="!isEditNodeName"
:title="title"
v-highlight="{ value: fullSearchValue, class: 'relation-views-node-title-highlight' }"
>{{ title }}
</span>
<span class="relation-views-node-title" v-if="!isEditNodeName" :title="title">{{ title }}</span>
<a-input
ref="input"
@blur="changeNodeName"
@@ -73,7 +67,10 @@
key="editNodeName"
><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item
>
<a-menu-item key="batch"><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item>
<a-menu-item
key="batch"
><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item
>
</template>
<template v-else>
<a-menu-item
@@ -106,17 +103,20 @@
<script>
import { updateCI } from '../../../api/ci.js'
import highlight from '@/directive/highlight'
export default {
name: 'ContextMenu',
directives: {
highlight,
},
props: {
treeNodeData: {
type: Object,
default: () => {},
title: {
type: String,
default: '',
},
number: {
type: Number,
default: 0,
},
treeKey: {
type: String,
default: '',
},
levels: {
type: Array,
@@ -130,6 +130,10 @@ export default {
type: Object,
default: () => {},
},
isLeaf: {
type: Boolean,
default: () => false,
},
ciTypeIcons: {
type: Object,
default: () => {},
@@ -142,10 +146,6 @@ export default {
type: Array,
default: () => [],
},
fullSearchValue: {
type: String,
default: '',
},
},
data() {
return {
@@ -201,21 +201,6 @@ export default {
showCheckbox() {
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
},
title() {
return this.treeNodeData.title
},
number() {
return this.treeNodeData.number
},
treeKey() {
return this.treeNodeData.key
},
isLeaf() {
return this.treeNodeData.isLeaf
},
showName() {
return this.treeNodeData.showName
},
},
methods: {
onContextMenuClick(treeKey, menuKey) {
@@ -245,11 +230,8 @@ export default {
.split('%')
const unique = Object.keys(JSON.parse(ci[2]))[0]
const ciId = Number(ci[0])
let editAttrName = unique
if (this.showName) {
editAttrName = this.showName
}
updateCI(ciId, { [editAttrName]: value }).then((res) => {
updateCI(ciId, { [unique]: value }).then((res) => {
this.$message.success(this.$t('updateSuccess'))
this.$emit('updateTreeData', ciId, value)
})
@@ -262,6 +244,8 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.relation-views-node {
width: 100%;
display: inline-flex;
@@ -330,9 +314,7 @@ export default {
</style>
<style lang="less">
.relation-views-node-title-highlight {
color: @func-color_1;
}
@import '~@/style/static.less';
.relation-views-left {
ul:has(.relation-views-node-checkbox) > li > ul {
margin-left: 26px;

View File

@@ -535,6 +535,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.resource-search {
margin-bottom: -24px;

View File

@@ -50,6 +50,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.tree-views-node {
width: 100%;
display: inline-flex;

View File

@@ -0,0 +1,33 @@
import { getCompanyInfo } from '@/api/company'
const logo = {
state: {
file_name: '',
small_file_name: ''
},
mutations: {
SET_FILENAME: (state, name) => {
state.file_name = name
},
SET_SMALL_FILENAME: (state, name) => {
state.small_file_name = name
}
},
actions: {
getCompanyInfo({ commit }) {
return new Promise((resolve, reject) => {
getCompanyInfo().then(res => {
if (res.info) {
commit('SET_FILENAME', res.info.logoName)
commit('SET_SMALL_FILENAME', res.info.smallLogoName)
resolve(res.info)
}
}).catch(err => {
console.log('获取失败', err)
reject(err)
})
})
}
}
}
export default logo

View File

@@ -5,6 +5,7 @@ import Vuex from 'vuex'
import app from './global/app'
import user from './global/user'
import routes from './global/routes'
import logo from './global/logo'
import notice from './global/notice'
import getters from './global/getters'
import appConfig from '@/config/app'
@@ -16,6 +17,7 @@ const store = new Vuex.Store({
app,
user,
routes,
logo,
notice
},
state: {

View File

@@ -1352,11 +1352,3 @@ body {
.ant-tree li .ant-tree-node-content-wrapper:hover {
background-color: @primary-color_3;
}
.ant-pagination-options-size-changer.ant-select {
margin-right: 0;
}
.ant-form-explain{
font-size: 12px;
}

View File

@@ -130,30 +130,22 @@ export const isEmptySubDepartments = (item) => {
// format部门员工树
export const formatOption = (data, idType = 1, isDisabledAllCompany, departmentKey = 'department_id', employeeKey = 'employee_id') => {
// idType 1 表示 员工id为`${departmentKey}-${employeeKey}`
// 2 表示 department-${departmentKey} employee-${employeeKey}
// 3 表示 departmentKey employeeKey
// idType 1 表示 员工id为`${department_id}-${employee_id}`
// 2 表示 department-${department_id} employee-${employee_id}
let _data = _.cloneDeep(data)
_data = _data.filter((item) => {
return item.employees.length || (item.sub_departments.length && !isEmptySubDepartments(item))
})
const switchEmployeeIdType = (item, employee) => {
switch (idType) {
case 1: return `${item[departmentKey]}-${employee[employeeKey]}`
case 2: return `employee-${employee[employeeKey]}`
case 3: return `${employee[employeeKey]}`
}
}
_data.forEach((item) => {
if (isDisabledAllCompany) {
item.isDisabled = !item.department_id
}
item.id = [1, 3].includes(idType) ? item[departmentKey] : `department-${item[departmentKey]}`
item.id = idType === 1 ? item[departmentKey] : `department-${item[departmentKey]}`
item.label = item.department_name
item.children = [
...formatOption(
item.sub_departments.map((dep) => {
return { ...dep, id: [1, 3].includes(idType) ? dep[departmentKey] : `department-${dep[departmentKey]}`, label: dep.department_name }
return { ...dep, id: idType === 1 ? dep[departmentKey] : `department-${dep[departmentKey]}`, label: dep.department_name }
}),
idType,
isDisabledAllCompany,
@@ -161,7 +153,7 @@ export const formatOption = (data, idType = 1, isDisabledAllCompany, departmentK
employeeKey
),
...item.employees.map((employee) => {
return { ...employee, id: switchEmployeeIdType(item, employee), label: employee.nickname }
return { ...employee, id: idType === 1 ? `${item[departmentKey]}-${employee[employeeKey]}` : `employee-${employee[employeeKey]}`, label: employee.nickname }
}),
]
})

View File

@@ -284,6 +284,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.notice-center-left {
color: rgba(0, 0, 0, 0.7);

View File

@@ -3,62 +3,140 @@
<a-form-model ref="infoData" :model="infoData" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rule">
<SpanTitle>{{ $t('cs.companyInfo.spanCompany') }}</SpanTitle>
<a-form-model-item :label="$t('cs.companyInfo.name')" prop="name">
<a-input v-model="infoData.name" :disabled="!isEditable" />
<a-input v-model="infoData.name" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.description')">
<a-input v-model="infoData.description" type="textarea" :disabled="!isEditable" />
<a-input v-model="infoData.description" type="textarea" :disabled="!isEditable"/>
</a-form-model-item>
<SpanTitle>{{ $t('cs.companyInfo.spanAddress') }}</SpanTitle>
<a-form-model-item :label="$t('cs.companyInfo.country')">
<a-input v-model="infoData.country" :disabled="!isEditable" />
<a-input v-model="infoData.country" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.city')">
<a-input v-model="infoData.city" :disabled="!isEditable" />
<a-input v-model="infoData.city" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.address')">
<a-input v-model="infoData.address" :disabled="!isEditable" />
<a-input v-model="infoData.address" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.postcode')">
<a-input v-model="infoData.postCode" :disabled="!isEditable" />
<a-input v-model="infoData.postCode" :disabled="!isEditable"/>
</a-form-model-item>
<SpanTitle>{{ $t('cs.companyInfo.spanContract') }}</SpanTitle>
<a-form-model-item :label="$t('cs.companyInfo.website')">
<a-input v-model="infoData.website" :disabled="!isEditable" />
<a-input v-model="infoData.website" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.phone')" prop="phone">
<a-input v-model="infoData.phone" :disabled="!isEditable" />
<a-input v-model="infoData.phone" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.faxCode')" prop="faxCode">
<a-input v-model="infoData.faxCode" :disabled="!isEditable" />
<a-input v-model="infoData.faxCode" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.email')" prop="email">
<a-input v-model="infoData.email" :disabled="!isEditable" />
<a-input v-model="infoData.email" :disabled="!isEditable"/>
</a-form-model-item>
<SpanTitle>{{ $t('cs.companyInfo.spanLogo') }}</SpanTitle>
<a-form-model-item :label="$t('cs.companyInfo.messenger')" prop="messenger">
<a-input v-model="infoData.messenger" :disabled="!isEditable" />
<a-input v-model="infoData.messenger" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.domainName')" prop="domainName">
<a-input v-model="infoData.domainName" :disabled="!isEditable" />
<a-input v-model="infoData.domainName" :disabled="!isEditable"/>
</a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.logo')">
<a-space>
<a-upload
:disabled="!isEditable"
name="avatar"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:customRequest="customRequest"
:before-upload="beforeUpload"
:style="{ width: '400px', height: '80px' }"
accept=".png,.jpg,.jpeg"
>
<div
class="ops-setting-companyinfo-upload-show"
v-if="infoData.logoName"
:style="{ width: '400px', height: '80px' }"
@click="eidtImageOption.type = 'Logo'"
>
<img :src="`/api/common-setting/v1/file/${infoData.logoName}`" alt="avatar"/>
<a-icon
v-if="isEditable"
type="minus-circle"
theme="filled"
class="delete-icon"
@click.stop="deleteLogo"
/>
</div>
<div v-else @click="eidtImageOption.type = 'Logo'">
<a-icon type="plus"/>
<div class="ant-upload-text">{{ $t('cs.companyInfo.upload') }}</div>
</div>
</a-upload>
<a-upload
:disabled="!isEditable"
name="avatar"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:customRequest="customRequest"
:before-upload="beforeUpload"
:style="{ width: '82px', height: '82px' }"
accept=".png,.jpg,.jpeg"
>
<div
class="ops-setting-companyinfo-upload-show"
v-if="infoData.smallLogoName"
:style="{ width: '82px', height: '82px' }"
@click="eidtImageOption.type = 'SmallLogo'"
>
<img :src="`/api/common-setting/v1/file/${infoData.smallLogoName}`" alt="avatar"/>
<a-icon
v-if="isEditable"
type="minus-circle"
theme="filled"
class="delete-icon"
@click.stop="deleteSmallLogo"
/>
</div>
<div v-else @click="eidtImageOption.type = 'SmallLogo'">
<a-icon type="plus"/>
<div class="ant-upload-text">{{ $t('cs.companyInfo.upload') }}</div>
</div>
</a-upload>
</a-space>
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 3 }" v-if="isEditable">
<a-button type="primary" @click="onSubmit"> {{ $t('save') }}</a-button>
<a-button ghost type="primary" style="margin-left: 28px" @click="resetForm"> {{ $t('reset') }}</a-button>
</a-form-model-item>
</a-form-model>
<edit-image
v-if="showEditImage"
:show="showEditImage"
:image="editImage"
:title="eidtImageOption.title"
:eidtImageOption="eidtImageOption"
@save="submitImage"
@close="showEditImage = false"
/>
</div>
</template>
<script>
import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company'
import { mapState } from 'vuex'
import { postImageFile } from '@/api/file'
import { mapMutations, mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import EditImage from '../components/EditImage.vue'
import { mixinPermissions } from '@/utils/mixin'
export default {
name: 'CompanyInfo',
mixins: [mixinPermissions],
components: { SpanTitle },
components: { SpanTitle, EditImage },
data() {
return {
labelCol: { span: 3 },
@@ -74,10 +152,14 @@ export default {
phone: '',
faxCode: '',
email: '',
logoName: '',
smallLogoName: '',
messenger: '',
domainName: '',
},
getId: -1,
showEditImage: false,
editImage: null
}
},
async mounted() {
@@ -128,8 +210,26 @@ export default {
],
}
},
eidtImageOption () {
return {
type: 'Logo',
fixedNumber: [15, 4],
title: this.$t('cs.companyInfo.editCompanyLogo'),
previewWidth: '200px',
previewHeight: '40px',
autoCropWidth: 200,
autoCropHeight: 40,
}
}
},
methods: {
...mapMutations(['SET_FILENAME', 'SET_SMALL_FILENAME']),
deleteLogo() {
this.infoData.logoName = ''
},
deleteSmallLogo() {
this.infoData.smallLogoName = ''
},
async onSubmit() {
this.$refs.infoData.validate(async (valid) => {
if (valid) {
@@ -138,6 +238,8 @@ export default {
} else {
await putCompanyInfo(this.getId, this.infoData)
}
this.SET_FILENAME(this.infoData.logoName)
this.SET_SMALL_FILENAME(this.infoData.smallFileName)
this.$message.success(this.$t('saveSuccess'))
} else {
this.$message.warning(this.$t('cs.companyInfo.checkInputCorrect'))
@@ -157,10 +259,70 @@ export default {
phone: '',
faxCode: '',
email: '',
logoName: '',
smallLogoName: '',
messenger: '',
domainName: '',
}
},
customRequest(file) {
const reader = new FileReader()
var self = this
if (this.eidtImageOption.type === 'Logo') {
this.eidtImageOption = {
type: 'Logo',
fixedNumber: [20, 4],
title: this.$t('cs.companyInfo.editCompanyLogo'),
previewWidth: '200px',
previewHeight: '40px',
autoCropWidth: 200,
autoCropHeight: 40,
}
} else if (this.eidtImageOption.type === 'SmallLogo') {
this.eidtImageOption = {
type: 'SmallLogo',
fixedNumber: [4, 4],
title: this.$t('cs.companyInfo.editCompanyLogoSmall'),
previewWidth: '80px',
previewHeight: '80px',
autoCropWidth: 250,
autoCropHeight: 250,
}
}
reader.onload = function (e) {
let result
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
result = window.URL.createObjectURL(new Blob([e.target.result]))
} else {
result = e.target.result
}
self.editImage = result
self.showEditImage = true
}
reader.readAsDataURL(file.file)
},
submitImage(file) {
postImageFile(file).then((res) => {
if (res.file_name) {
if (this.eidtImageOption.type === 'Logo') {
this.infoData.logoName = res.file_name
} else if (this.eidtImageOption.type === 'SmallLogo') {
this.infoData.smallLogoName = res.file_name
}
} else {
}
})
},
beforeUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error(this.$t('cs.companyInfo.imageSizeLimit2MB'))
}
return isLt2M
},
},
}
</script>
@@ -169,7 +331,7 @@ export default {
.ops-setting-companyinfo {
padding-top: 15px;
background-color: #fff;
border-radius: @border-radius-box;
border-radius: 15px;
overflow: auto;
margin-bottom: -24px;
.ops-setting-companyinfo-upload-show {

View File

@@ -155,6 +155,7 @@ export default {
</script>
<style lang="less">
@import '~@/style/static.less';
ul,
li {
list-style: none;

View File

@@ -862,13 +862,14 @@ export default {
}
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.ops-setting-structure {
margin-bottom: -24px;
width: 100%;
.ops-setting-structure-sidebar {
height: 100%;
padding: 15px;
border-radius: @border-radius-box;
border-radius: 15px;
overflow-y: hidden;
&:hover {
overflow-y: overlay;
@@ -1025,7 +1026,7 @@ export default {
padding: 12px;
background-color: #fff;
overflow-y: auto;
border-radius: @border-radius-box;
border-radius: 15px;
.ops-setting-structure-main-controller {
// height: 70px;
margin-bottom: 12px;

View File

@@ -64,6 +64,7 @@ name: 'SearchForm',
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.search-form-bar-filter {
background-color: rgb(240, 245, 255);
.ops_display_wrapper();

View File

@@ -137,7 +137,7 @@ export default {
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: @border-radius-box;
border-radius: 15px;
.notice-dingding-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;

View File

@@ -117,7 +117,7 @@ export default {
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: @border-radius-box;
border-radius: 15px;
.notice-feishu-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;

View File

@@ -138,7 +138,7 @@ export default {
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: @border-radius-box;
border-radius: 15px;
.notice-wx-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;

View File

@@ -354,6 +354,7 @@ export default {
</script>
<style lang="less" scoped>
@import '~@/style/static.less';
.setting-person {
display: flex;
flex-direction: row;
@@ -362,7 +363,7 @@ export default {
height: 400px;
margin-right: 24px;
background-color: #fff;
border-radius: @border-radius-box;
border-radius: 15px;
padding-top: 15px;
.setting-person-left-item {
cursor: pointer;
@@ -387,7 +388,7 @@ export default {
width: 800px;
height: 700px;
background-color: #fff;
border-radius: @border-radius-box;
border-radius: 15px;
padding: 24px 48px;
.setting-person-right-disabled {
background-color: #custom_colors[color_2];

View File

@@ -91,12 +91,7 @@ module.exports = {
},
},
},
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [path.resolve(__dirname, './src/style/static.less')],
},
},
devServer: {
disableHostCheck: true,
port: process.env.DEV_PORT || 8000,

View File

@@ -32,8 +32,6 @@ services:
container_name: cmdb-cache
environment:
TZ: Asia/Shanghai
volumes:
- cache-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
@@ -45,7 +43,7 @@ services:
- redis
cmdb-api:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.3
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.2
# build:
# context: .
# target: cmdb-api
@@ -68,6 +66,9 @@ services:
flask common-check-new-columns
gunicorn --workers=4 autoapp:app -b 0.0.0.0:5000 -D
#nohup celery -A celery_worker.celery worker -E -Q one_cmdb_async --autoscale=2,5 > one_cmdb_async.log 2>&1 &
#nohup celery -A celery_worker.celery worker -E -Q acl_async --concurrency=2 > one_acl_async.log 2>&1 &
#
celery -A celery_worker.celery worker -E -Q one_cmdb_async --autoscale=4,1 --logfile=one_cmdb_async.log -D
celery -A celery_worker.celery worker -E -Q acl_async --logfile=one_acl_async.log --autoscale=2,1 -D
@@ -84,7 +85,7 @@ services:
- cmdb-api
cmdb-ui:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.3
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.2
# build:
# context: .
# target: cmdb-ui
@@ -112,9 +113,6 @@ volumes:
db-data:
driver: local
name: cmdb_db-data
cache-data:
driver: local
name: cmdb_cache-data
networks:
new: