mirror of https://github.com/veops/cmdb.git
Merge branch 'master' into fix_bug_1217
This commit is contained in:
commit
c3ae1f3ffa
|
@ -0,0 +1,79 @@
|
|||
line-length = 120
|
||||
cache-dir = ".ruff_cache"
|
||||
target-version = "py310"
|
||||
unsafe-fixes = true
|
||||
show-fixes = true
|
||||
|
||||
[lint]
|
||||
select = [
|
||||
"E",
|
||||
"F",
|
||||
"I",
|
||||
"TCH",
|
||||
# W
|
||||
"W505",
|
||||
# PT
|
||||
"PT018",
|
||||
# SIM
|
||||
"SIM101",
|
||||
"SIM114",
|
||||
# PGH
|
||||
"PGH004",
|
||||
# PL
|
||||
"PLE1142",
|
||||
# RUF
|
||||
"RUF100",
|
||||
# UP
|
||||
"UP007"
|
||||
]
|
||||
preview = true
|
||||
ignore = ["FURB101"]
|
||||
|
||||
[lint.flake8-pytest-style]
|
||||
mark-parentheses = false
|
||||
parametrize-names-type = "list"
|
||||
parametrize-values-row-type = "list"
|
||||
parametrize-values-type = "tuple"
|
||||
|
||||
[lint.flake8-unused-arguments]
|
||||
ignore-variadic-names = true
|
||||
|
||||
[lint.isort]
|
||||
lines-between-types = 1
|
||||
order-by-type = true
|
||||
|
||||
[lint.per-file-ignores]
|
||||
"**/api/v1/*.py" = ["TCH"]
|
||||
"**/model/*.py" = ["TCH003"]
|
||||
"**/models/__init__.py" = ["F401", "F403"]
|
||||
"**/tests/*.py" = ["E402"]
|
||||
"celery_worker.py" = ["F401"]
|
||||
"api/views/entry.py" = ["I001"]
|
||||
"migrations/*.py" = ["I001", "E402"]
|
||||
"*.py" = ["I001"]
|
||||
"api/views/common_setting/department.py" = ["F841"]
|
||||
"api/lib/common_setting/upload_file.py" = ["F841"]
|
||||
"api/lib/common_setting/acl.py" = ["F841"]
|
||||
"**/__init__.py" = ["F822"]
|
||||
"api/tasks/*.py" = ["E722"]
|
||||
"api/views/cmdb/*.py" = ["E722"]
|
||||
"api/views/acl/*.py" = ["E722"]
|
||||
"api/lib/secrets/*.py" = ["E722", "F841"]
|
||||
"api/lib/utils.py" = ["E722", "E731"]
|
||||
"api/lib/perm/authentication/cas/*" = ["E113", "F841"]
|
||||
"api/lib/perm/acl/*" = ["E722"]
|
||||
"api/lib/*" = ["E721", "F722"]
|
||||
"api/lib/cmdb/*" = ["F722", "E722"]
|
||||
"api/lib/cmdb/search/ci/es/search.py" = ["F841", "SIM114"]
|
||||
"api/lib/cmdb/search/ci/db/search.py" = ["F841"]
|
||||
"api/lib/cmdb/value.py" = ["F841"]
|
||||
"api/lib/cmdb/history.py" = ["E501"]
|
||||
"api/commands/common.py" = ["E722"]
|
||||
"api/commands/click_cmdb.py" = ["E722"]
|
||||
"api/lib/perm/auth.py" = ["SIM114"]
|
||||
|
||||
[format]
|
||||
preview = true
|
||||
quote-style = "single"
|
||||
docstring-code-format = true
|
||||
skip-magic-trailing-comma = false
|
|
@ -346,7 +346,7 @@ def cmdb_inner_secrets_init(address):
|
|||
if valid_address(address):
|
||||
token = current_app.config.get("INNER_TRIGGER_TOKEN", "") if not token else token
|
||||
if not token:
|
||||
token = click.prompt(f'Enter root token', hide_input=True, confirmation_prompt=False)
|
||||
token = click.prompt('Enter root token', hide_input=True, confirmation_prompt=False)
|
||||
assert token is not None
|
||||
resp = requests.post("{}/api/v0.1/secrets/auto_seal".format(address.strip("/")),
|
||||
headers={"Inner-Token": token})
|
||||
|
|
|
@ -415,7 +415,7 @@ class AttributeManager(object):
|
|||
db.session.rollback()
|
||||
current_app.logger.error("update attribute error, {0}".format(str(e)))
|
||||
|
||||
return abort(400, ErrFormat.update_attribute_failed.format(("id=".format(_id))))
|
||||
return abort(400, ErrFormat.update_attribute_failed.format(("id={}".format(_id))))
|
||||
|
||||
new = attr.to_dict()
|
||||
if not new['choice_web_hook'] and new['is_choice']:
|
||||
|
|
|
@ -862,15 +862,15 @@ class CITypeRelationManager(object):
|
|||
|
||||
graph = nx.DiGraph()
|
||||
|
||||
def get_children(_id):
|
||||
def get_children(_id, _graph):
|
||||
children = CITypeRelation.get_by(parent_id=_id, to_dict=False)
|
||||
|
||||
for i in children:
|
||||
if i.child_id != _id:
|
||||
graph.add_edge(i.parent_id, i.child_id)
|
||||
get_children(i.child_id)
|
||||
_graph.add_edge(i.parent_id, i.child_id)
|
||||
get_children(i.child_id, _graph)
|
||||
|
||||
get_children(source_type_id)
|
||||
get_children(source_type_id, graph)
|
||||
|
||||
paths = list(nx.all_simple_paths(graph, source_type_id, target_type_ids))
|
||||
|
||||
|
|
|
@ -16,8 +16,9 @@ class ErrFormat(CommonErrFormat):
|
|||
argument_file_not_found = _l("The file doesn't seem to be uploaded") # 文件似乎并未上传
|
||||
|
||||
attribute_not_found = _l("Attribute {} does not exist!") # 属性 {} 不存在!
|
||||
# 该属性是模型的唯一标识,不能被删除!
|
||||
attribute_is_unique_id = _l(
|
||||
"This attribute is the unique identifier of the model and cannot be deleted!") # 该属性是模型的唯一标识,不能被删除!
|
||||
"This attribute is the unique identifier of the model and cannot be deleted!")
|
||||
attribute_is_ref_by_type = _l(
|
||||
"This attribute is referenced by model {} and cannot be deleted!") # 该属性被模型 {} 引用, 不能删除!
|
||||
attribute_value_type_cannot_change = _l(
|
||||
|
@ -129,7 +130,8 @@ class ErrFormat(CommonErrFormat):
|
|||
adr_default_ref_once = _l("The default auto-discovery rule is already referenced by model {}!")
|
||||
# unique_key方法必须返回非空字符串!
|
||||
adr_unique_key_required = _l("The unique_key method must return a non-empty string!")
|
||||
adr_plugin_attributes_list_required = _l("The attributes method must return a list") # attributes方法必须返回的是list
|
||||
# attributes方法必须返回的是list
|
||||
adr_plugin_attributes_list_required = _l("The attributes method must return a list")
|
||||
# attributes方法返回的list不能为空!
|
||||
adr_plugin_attributes_list_no_empty = _l("The list returned by the attributes method cannot be empty!")
|
||||
# 只有管理员才可以定义执行机器为: 所有节点!
|
||||
|
|
|
@ -107,3 +107,12 @@ FROM
|
|||
WHERE c_value_index_datetime.value LIKE "{0}") AS {1}
|
||||
GROUP BY {1}.ci_id
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_NO_ATTR_IN = """
|
||||
SELECT *
|
||||
FROM
|
||||
(SELECT c_value_index_texts.ci_id
|
||||
FROM c_value_index_texts
|
||||
WHERE c_value_index_texts.value in ({0})) AS {1}
|
||||
GROUP BY {1}.ci_id
|
||||
"""
|
|
@ -27,6 +27,7 @@ from api.lib.cmdb.search.ci.db.query_sql import FACET_QUERY
|
|||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_ATTR_NAME
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_ID
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_NO_ATTR
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_NO_ATTR_IN
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_TYPE
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_UNION_CI_ATTRIBUTE_IS_NULL
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
|
@ -527,10 +528,15 @@ class Search(object):
|
|||
for q in queries:
|
||||
_query_sql = ""
|
||||
if isinstance(q, dict):
|
||||
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias, is_sub=True)
|
||||
# current_app.logger.info(_query_sql)
|
||||
# current_app.logger.info((operator, is_first, alias))
|
||||
operator = q['operator']
|
||||
current_app.logger.debug("Dict query content: queries=%s, operator=%s", q['queries'], q['operator'])
|
||||
if len(q['queries']) == 1 and ";" in q['queries'][0]:
|
||||
values = q['queries'][0].split(";")
|
||||
in_values = ",".join("'{0}'".format(v) for v in values)
|
||||
_query_sql = QUERY_CI_BY_NO_ATTR_IN.format(in_values, alias)
|
||||
operator = q['operator']
|
||||
else:
|
||||
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias, is_sub=True)
|
||||
operator = q['operator']
|
||||
|
||||
elif ":" in q and not q.startswith("*"):
|
||||
alias, _query_sql, operator = self.__query_by_attr(q, queries, alias, is_sub)
|
||||
|
|
|
@ -55,7 +55,6 @@ def str2datetime(x):
|
|||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M")
|
||||
|
||||
|
||||
|
||||
class ValueTypeMap(object):
|
||||
deserialize = {
|
||||
ValueTypeEnum.INT: string2int,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import functools
|
||||
|
||||
from flask import abort, session
|
||||
from flask import abort, session, current_app
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
|
@ -15,6 +15,7 @@ def perms_role_required(app_name, resource_type_name, resource_name, perm, role_
|
|||
try:
|
||||
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"acl role_has_perms err: {e}")
|
||||
# resource_type not exist, continue check role
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
|
||||
|
|
|
@ -476,7 +476,7 @@ class EditDepartmentInACL(object):
|
|||
for employee in e_list:
|
||||
employee_acl_rid = employee.get('e_acl_rid')
|
||||
if employee_acl_rid == 0:
|
||||
result.append(f"employee_acl_rid == 0")
|
||||
result.append("employee_acl_rid == 0")
|
||||
continue
|
||||
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||
|
||||
|
@ -501,8 +501,8 @@ class EditDepartmentInACL(object):
|
|||
acl.remove_user_from_role(employee.get('e_acl_rid'), payload)
|
||||
current_app.logger.info(f"remove {employee.get('e_acl_rid')} from {d_acl_rid}")
|
||||
except Exception as e:
|
||||
result.append(
|
||||
f"remove_user_from_role employee_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}")
|
||||
err = f"remove_user_from_role e_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}"
|
||||
result.append(err)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -548,7 +548,7 @@ class EditDepartmentInACL(object):
|
|||
for employee in e_list:
|
||||
employee_acl_rid = employee.get('e_acl_rid')
|
||||
if employee_acl_rid == 0:
|
||||
result.append(f"employee_acl_rid == 0")
|
||||
result.append("employee_acl_rid == 0")
|
||||
continue
|
||||
|
||||
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||
|
|
|
@ -4,7 +4,7 @@ import traceback
|
|||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from flask import abort
|
||||
from flask import abort, current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import or_, literal_column, func, not_, and_
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
@ -478,7 +478,7 @@ class EmployeeCRUD(object):
|
|||
Employee.deleted == 0,
|
||||
Employee.block == block,
|
||||
]
|
||||
if type(department_id) == list:
|
||||
if isinstance(department_id, list):
|
||||
if len(department_id) == 0:
|
||||
return []
|
||||
else:
|
||||
|
@ -702,6 +702,7 @@ class EmployeeCRUD(object):
|
|||
try:
|
||||
last_login = datetime.strptime(last_login, '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"strptime {last_login} err: {e}")
|
||||
last_login = datetime.now()
|
||||
else:
|
||||
last_login = datetime.now()
|
||||
|
@ -712,6 +713,7 @@ class EmployeeCRUD(object):
|
|||
)
|
||||
return last_login
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"update last_login err: {e}")
|
||||
return
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import requests
|
|||
|
||||
from api.lib.common_setting.const import BotNameMap
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import CompanyInfo, NoticeConfig
|
||||
from api.models.common_setting import NoticeConfig
|
||||
from wtforms import Form
|
||||
from wtforms import StringField
|
||||
from wtforms import validators
|
||||
|
|
|
@ -48,7 +48,9 @@ class CMDBApp(BaseApp):
|
|||
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
|
||||
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
|
||||
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read", "create_plugin", "update_plugin", "delete_plugin"]},
|
||||
{"page": "Auto_Discovery", "page_cn": "自动发现",
|
||||
"perms": ["read", "create_plugin", "update_plugin", "delete_plugin"]
|
||||
},
|
||||
{"page": "TopologyView", "page_cn": "拓扑视图",
|
||||
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
|
||||
"create_topology_view"],
|
||||
|
|
|
@ -6,7 +6,7 @@ from functools import wraps
|
|||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.perm.acl.cache import AppCache, AppAccessTokenCache
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
import six
|
||||
from flask import abort
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import base64
|
||||
from typing import Set
|
||||
|
||||
import elasticsearch
|
||||
import redis
|
||||
|
|
|
@ -131,7 +131,7 @@ class EmployeeChangePasswordWithACLID(APIView):
|
|||
if not password:
|
||||
abort(400, ErrFormat.password_is_required)
|
||||
|
||||
data = EmployeeCRUD.change_password_by_uid(_uid, password)
|
||||
EmployeeCRUD.change_password_by_uid(_uid, password)
|
||||
return self.jsonify(200)
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import magic
|
|||
|
||||
from api.lib.common_setting.const import MIMEExtMap
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
||||
from api.lib.common_setting.upload_file import generate_new_file_name, CommonFileCRUD
|
||||
from api.resource import APIView
|
||||
|
||||
prefix = '/file'
|
||||
|
|
|
@ -58,3 +58,4 @@ python-magic==0.4.27
|
|||
jsonpath==0.82.2
|
||||
networkx>=3.1
|
||||
ipaddress>=1.0.23
|
||||
ruff==0.8.3
|
||||
|
|
|
@ -314,6 +314,9 @@ const cmdb_en = {
|
|||
enum: 'Enum',
|
||||
ciGrantTip: `Filter conditions can be changed dynamically using {{}} referenced variables, currently user variables are supported, such as {{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: 'Please search for resource keywords',
|
||||
columnSearchInputTip: '192.168.1.1\n192.168.1.2\n192.168.1.3',
|
||||
rowSearchMode: 'Single Row Search',
|
||||
columnSearchMode: 'Multi Row Search',
|
||||
resourceSearch: 'Resource Search',
|
||||
recentSearch: 'Recent Search',
|
||||
myCollection: 'My Collection',
|
||||
|
|
|
@ -314,6 +314,9 @@ const cmdb_zh = {
|
|||
enum: '枚举',
|
||||
ciGrantTip: `筛选条件可使用{{}}引用变量实现动态变化,目前支持用户变量,如{{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: '请搜索资源关键字',
|
||||
columnSearchInputTip: '192.168.1.1\n192.168.1.2\n192.168.1.3',
|
||||
rowSearchMode: '单行搜索',
|
||||
columnSearchMode: '多行搜索',
|
||||
resourceSearch: '资源搜索',
|
||||
recentSearch: '最近搜索',
|
||||
myCollection: '我的收藏',
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : null,
|
||||
initialValue: attr.default && attr.default.default !== undefined && attr.default.default !== null ? attr.default.default : null,
|
||||
},
|
||||
]"
|
||||
style="width: 100%"
|
||||
|
@ -148,6 +148,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
@ -210,7 +211,7 @@ export default {
|
|||
},
|
||||
|
||||
getChoiceDefault(attr) {
|
||||
if (!attr?.default?.default) {
|
||||
if (_.isNil(attr?.default?.default)) {
|
||||
return attr.is_list ? [] : null
|
||||
}
|
||||
|
||||
|
|
|
@ -659,7 +659,7 @@ export default {
|
|||
} else {
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
default_value: _record.default && _record.default.default ? _record.default.default : null,
|
||||
default_value: _record?.default?.default ?? null,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ export default {
|
|||
saveCondition(isSubmit) {
|
||||
this.$refs.conditionFilterRef.handleSubmit()
|
||||
this.$nextTick(() => {
|
||||
this.$emit('saveCondition', isSubmit)
|
||||
this.$emit('saveCondition', isSubmit, this.$parent.isColumnSearch ? 'column' : 'normal')
|
||||
this.visible = false
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,29 +1,59 @@
|
|||
<template>
|
||||
<div :class="['search-input', classType ? 'search-input-' + classType : '']">
|
||||
<a-input
|
||||
:value="searchValue"
|
||||
class="search-input-component"
|
||||
:placeholder="$t('cmdb.ciType.searchInputTip')"
|
||||
@change="handleChangeSearchValue"
|
||||
@pressEnter="saveCondition(true)"
|
||||
>
|
||||
<a-icon
|
||||
class="search-input-component-icon"
|
||||
slot="prefix"
|
||||
type="search"
|
||||
@click="saveCondition(true)"
|
||||
/>
|
||||
</a-input>
|
||||
<FilterPopover
|
||||
ref="filterPpoverRef"
|
||||
:CITypeGroup="CITypeGroup"
|
||||
:allAttributesList="allAttributesList"
|
||||
:expression="expression"
|
||||
:selectCITypeIds="selectCITypeIds"
|
||||
@changeFilter="changeFilter"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@saveCondition="saveCondition"
|
||||
/>
|
||||
<div :class="['search-input', classType ? 'search-input-' + classType : '', { 'column-search-mode': isColumnSearch }]">
|
||||
<div class="search-area">
|
||||
<div v-show="!isColumnSearch" class="input-wrapper">
|
||||
<a-input
|
||||
:value="searchValue"
|
||||
class="search-input-component"
|
||||
:placeholder="$t('cmdb.ciType.searchInputTip')"
|
||||
@change="handleChangeSearchValue"
|
||||
@pressEnter="saveCondition(true, 'normal')"
|
||||
/>
|
||||
<a-icon
|
||||
class="search-icon"
|
||||
type="search"
|
||||
@click="saveCondition(true, 'normal')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="isColumnSearch" class="textarea-wrapper">
|
||||
<div class="textarea-container">
|
||||
<a-textarea
|
||||
:value="searchValue"
|
||||
class="column-search-component"
|
||||
:rows="4"
|
||||
:placeholder="$t('cmdb.ciType.columnSearchInputTip')"
|
||||
@change="handleChangeColumnSearchValue"
|
||||
@pressEnter="handlePressEnter"
|
||||
/>
|
||||
<a-icon
|
||||
class="search-icon"
|
||||
type="search"
|
||||
@click="saveCondition(true, 'column')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="operation-area">
|
||||
<FilterPopover
|
||||
ref="filterPpoverRef"
|
||||
:CITypeGroup="CITypeGroup"
|
||||
:allAttributesList="allAttributesList"
|
||||
:expression="expression"
|
||||
:selectCITypeIds="selectCITypeIds"
|
||||
@changeFilter="changeFilter"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@saveCondition="saveCondition"
|
||||
/>
|
||||
|
||||
<div class="column-search-btn" @click="toggleColumnSearch">
|
||||
<a-icon class="column-search-btn-icon" type="menu" />
|
||||
<span class="column-search-btn-title">
|
||||
{{ isColumnSearch ? $t('cmdb.ciType.rowSearchMode') : $t('cmdb.ciType.columnSearchMode') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="copyText" class="expression-display">
|
||||
<span class="expression-display-text">{{ copyText }}</span>
|
||||
|
@ -69,11 +99,12 @@ export default {
|
|||
classType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isColumnSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
// 复制文字展示,与实际文本复制内容区别在于,未选择模型时不展示所有模型拼接数据
|
||||
copyText() {
|
||||
|
@ -88,7 +119,14 @@ export default {
|
|||
textArray.push(exp)
|
||||
}
|
||||
if (this.searchValue) {
|
||||
textArray.push(`*${this.searchValue}*`)
|
||||
let processedValue = this.searchValue
|
||||
if (this.isColumnSearch) {
|
||||
const values = this.searchValue.split('\n').filter(v => v.trim())
|
||||
if (values.length) {
|
||||
processedValue = `(${values.join(';')})`
|
||||
}
|
||||
}
|
||||
textArray.push(`${!this.isColumnSearch ? '*' : ''}${processedValue}${!this.isColumnSearch ? '*' : ''}`)
|
||||
}
|
||||
|
||||
return textArray.length ? `q=${textArray.join(',')}` : ''
|
||||
|
@ -98,8 +136,8 @@ export default {
|
|||
updateAllAttributesList(value) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
},
|
||||
saveCondition(isSubmit) {
|
||||
this.$emit('saveCondition', isSubmit)
|
||||
saveCondition(isSubmit, searchType = 'normal') {
|
||||
this.$emit('saveCondition', isSubmit, searchType)
|
||||
},
|
||||
handleChangeSearchValue(e) {
|
||||
const value = e.target.value
|
||||
|
@ -125,7 +163,9 @@ export default {
|
|||
ciTypeIds.push(...ids)
|
||||
})
|
||||
}
|
||||
const copyText = `${ciTypeIds?.length ? `_type:(${ciTypeIds.join(';')})` : ''}${exp ? `,${exp}` : ''}${searchValue ? `,*${searchValue}*` : ''}`
|
||||
const copyText = `${ciTypeIds?.length ? `_type:(${ciTypeIds.join(';')})` : ''}${exp ? `,${exp}` : ''}${
|
||||
searchValue ? `,${!this.isColumnSearch ? '*' : ''}${searchValue}${!this.isColumnSearch ? '*' : ''}` : ''
|
||||
}`
|
||||
|
||||
this.$copyText(copyText)
|
||||
.then(() => {
|
||||
|
@ -134,6 +174,35 @@ export default {
|
|||
.catch(() => {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
|
||||
toggleColumnSearch() {
|
||||
this.$emit('toggleSearchMode', !this.isColumnSearch)
|
||||
this.saveCondition(false, !this.isColumnSearch ? 'column' : 'normal')
|
||||
},
|
||||
|
||||
handleChangeColumnSearchValue(e) {
|
||||
const value = e.target.value
|
||||
this.changeFilter({
|
||||
name: 'searchValue',
|
||||
value
|
||||
})
|
||||
},
|
||||
|
||||
handlePressEnter(e) {
|
||||
if (this.isColumnSearch) {
|
||||
// 列搜索模式下,按下 Enter 键时阻止默认行为并插入换行符
|
||||
e.preventDefault()
|
||||
const value = this.searchValue || ''
|
||||
const cursorPosition = e.target.selectionStart
|
||||
const newValue = value.slice(0, cursorPosition) + '\n' + value.slice(cursorPosition)
|
||||
this.changeFilter({
|
||||
name: 'searchValue',
|
||||
value: newValue
|
||||
})
|
||||
} else {
|
||||
this.saveCondition(true, 'normal')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,43 +211,107 @@ export default {
|
|||
<style lang="less" scoped>
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&-component {
|
||||
height: 100%;
|
||||
.search-area {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-height: 48px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
background-color: #FFFFFF;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
border-radius: 48px;
|
||||
overflow: hidden;
|
||||
|
||||
&-icon {
|
||||
color: #2F54EB;
|
||||
.search-input-component {
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #d9d9d9;
|
||||
font-size: 14px;
|
||||
border-radius: 8px;
|
||||
|
||||
/deep/ input {
|
||||
height: 100%;
|
||||
padding-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ & > input {
|
||||
height: 100%;
|
||||
margin-left: 10px;
|
||||
border: solid 1px transparent;
|
||||
box-shadow: none;
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #2F54EB;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: @primary-color;
|
||||
.textarea-wrapper {
|
||||
flex-grow: 1;
|
||||
|
||||
.textarea-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
|
||||
.column-search-component {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #d9d9d9;
|
||||
font-size: 14px;
|
||||
border-radius: 8px;
|
||||
padding-right: 35px;
|
||||
resize: none;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: #40a9ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
color: #2F54EB;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-after {
|
||||
height: 38px;
|
||||
justify-content: flex-start;
|
||||
.operation-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.search-input-component {
|
||||
max-width: 524px;
|
||||
.column-search-btn {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 13px;
|
||||
cursor: pointer;
|
||||
|
||||
&-icon {
|
||||
color: #2F54EB;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #2F54EB;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,5 +334,18 @@ export default {
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input-component,
|
||||
.column-search-component {
|
||||
&:hover {
|
||||
border-color: #40a9ff;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: #40a9ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
:searchValue="searchValue"
|
||||
:selectCITypeIds="selectCITypeIds"
|
||||
:expression="expression"
|
||||
:isColumnSearch="currentSearchType === 'column'"
|
||||
@changeFilter="changeFilter"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@saveCondition="saveCondition"
|
||||
@toggleSearchMode="handleToggleSearchMode"
|
||||
/>
|
||||
<HistoryList
|
||||
:recentList="recentList"
|
||||
|
@ -46,9 +48,11 @@
|
|||
:searchValue="searchValue"
|
||||
:selectCITypeIds="selectCITypeIds"
|
||||
:expression="expression"
|
||||
:isColumnSearch="currentSearchType === 'column'"
|
||||
@changeFilter="changeFilter"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@saveCondition="saveCondition"
|
||||
@toggleSearchMode="handleToggleSearchMode"
|
||||
/>
|
||||
<HistoryList
|
||||
:recentList="recentList"
|
||||
|
@ -172,6 +176,7 @@ export default {
|
|||
showInstanceDetail: false,
|
||||
detailCIId: -1,
|
||||
detailCITypeId: -1,
|
||||
currentSearchType: 'normal',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -240,7 +245,9 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
async saveCondition(isSubmit) {
|
||||
async saveCondition(isSubmit, searchType = 'normal') {
|
||||
this.currentSearchType = searchType
|
||||
|
||||
if (
|
||||
this.searchValue ||
|
||||
this.expression ||
|
||||
|
@ -253,7 +260,8 @@ export default {
|
|||
if (
|
||||
option.searchValue === this.searchValue &&
|
||||
option.expression === this.expression &&
|
||||
_.isEqual(option.ciTypeIds, this.selectCITypeIds)
|
||||
_.isEqual(option.ciTypeIds, this.selectCITypeIds) &&
|
||||
option.searchType === this.currentSearchType
|
||||
) {
|
||||
needDeleteList.push(item.id)
|
||||
} else {
|
||||
|
@ -279,7 +287,8 @@ export default {
|
|||
searchValue: this.searchValue,
|
||||
expression: this.expression,
|
||||
ciTypeIds: this.selectCITypeIds,
|
||||
ciTypeNames
|
||||
ciTypeNames,
|
||||
searchType: this.currentSearchType
|
||||
},
|
||||
name: '__recent__'
|
||||
})
|
||||
|
@ -290,7 +299,7 @@ export default {
|
|||
this.isSearch = true
|
||||
this.currentPage = 1
|
||||
this.hideDetail()
|
||||
this.loadInstance()
|
||||
this.loadInstance(this.currentSearchType)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -307,11 +316,19 @@ export default {
|
|||
this.getRecentList()
|
||||
},
|
||||
|
||||
async loadInstance() {
|
||||
const { selectCITypeIds, expression, searchValue } = this
|
||||
async loadInstance(searchType = 'normal') {
|
||||
const { selectCITypeIds, expression } = this
|
||||
let { searchValue } = this
|
||||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
|
||||
if (searchType === 'column' && searchValue) {
|
||||
const values = searchValue.split('\n').filter(v => v.trim())
|
||||
if (values.length) {
|
||||
searchValue = `(${values.join(';')})`
|
||||
}
|
||||
}
|
||||
|
||||
const ciTypeIds = [...selectCITypeIds]
|
||||
if (!ciTypeIds.length) {
|
||||
this.CITypeGroup.forEach((item) => {
|
||||
|
@ -322,7 +339,7 @@ export default {
|
|||
|
||||
const res = await searchCI({
|
||||
q: `${ciTypeIds?.length ? `_type:(${ciTypeIds.join(';')})` : ''}${exp ? `,${exp}` : ''}${
|
||||
searchValue ? `,*${searchValue}*` : ''
|
||||
searchValue ? `,${searchType === 'normal' ? '*' : ''}${searchValue}${searchType === 'normal' ? '*' : ''}` : ''
|
||||
}`,
|
||||
count: this.pageSize,
|
||||
page: this.currentPage,
|
||||
|
@ -389,7 +406,6 @@ export default {
|
|||
}
|
||||
this.ciTabList = ciTabList
|
||||
|
||||
// 处理引用属性
|
||||
const allAttr = []
|
||||
subscribedRes.map((item) => {
|
||||
allAttr.push(...item.attributes)
|
||||
|
@ -477,20 +493,21 @@ export default {
|
|||
this.searchValue = data?.searchValue || ''
|
||||
this.expression = data?.expression || ''
|
||||
this.selectCITypeIds = data?.ciTypeIds || []
|
||||
this.currentSearchType = data?.searchType || 'normal'
|
||||
|
||||
this.hideDetail()
|
||||
this.loadInstance()
|
||||
this.loadInstance(this.currentSearchType)
|
||||
},
|
||||
|
||||
handlePageSizeChange(_, pageSize) {
|
||||
this.pageSize = pageSize
|
||||
this.currentPage = 1
|
||||
this.loadInstance()
|
||||
this.loadInstance(this.currentSearchType)
|
||||
},
|
||||
|
||||
changePage(page) {
|
||||
this.currentPage = page
|
||||
this.loadInstance()
|
||||
this.loadInstance(this.currentSearchType)
|
||||
},
|
||||
|
||||
changeFilter(data) {
|
||||
|
@ -533,6 +550,10 @@ export default {
|
|||
clickFavor(data) {
|
||||
this.isSearch = true
|
||||
this.showDetail(data)
|
||||
},
|
||||
|
||||
handleToggleSearchMode(isColumn) {
|
||||
this.currentSearchType = isColumn ? 'column' : 'normal'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue