mirror of
https://github.com/veops/cmdb.git
synced 2025-09-05 21:07:01 +08:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5e2bedfb37 | ||
|
fb19353c70 |
@@ -1,13 +0,0 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
||||
|
||||
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
@@ -36,7 +36,7 @@
|
||||
- **多维度视图展示**:包括资源视图、层级视图、关系视图等,帮助运维人员全面管理资源。
|
||||
- **细粒度权限控制**:通过精确的访问控制和完备的操作日志保障系统的安全性。
|
||||
- **全面的资源搜索功能**:支持灵活的资源和关系搜索,快速定位和操作资源。
|
||||
- **集成 IP 地址管理(IPAM)和数据中心基础设施管理(DCIM)**:简化网络资源和数据中心设备的管理。
|
||||
- **集成 IP 地址管理(IPAM)和数据中心基础设施管理(DCIM)功能**:简化网络资源和数据中心设备的管理。
|
||||
|
||||
更多详细功能,请移步 [维易科技官网](https://veops.cn) 进行了解。
|
||||
|
||||
@@ -82,11 +82,6 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 关注我们
|
||||
|
||||
欢迎 Star 加关注,第一时间获取更新动态!
|
||||
|
||||

|
||||
|
||||
## 快速开始
|
||||
|
||||
@@ -108,7 +103,7 @@
|
||||
|
||||
## 接入公司
|
||||
|
||||
+ 欢迎使用开源CMDB的公司和团队,在 [#112](https://github.com/veops/cmdb/issues/112) 登记
|
||||
+ 欢迎使用开源CMDB的公司,在 [#112](https://github.com/veops/cmdb/issues/112) 登记
|
||||
|
||||
## 代码贡献
|
||||
我们欢迎所有开发者贡献代码,改善和扩展这个项目。请先阅读我们的[贡献指南](docs/CONTRIBUTING.md)。此外,您还可以通过社交媒体、活动和分享来支持 Veops 的开源。
|
||||
|
@@ -12,7 +12,6 @@ from sqlalchemy import func
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.auto_discovery.const import CLOUD_MAP
|
||||
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
|
||||
from api.lib.cmdb.auto_discovery.const import NET_DEVICE_NAMES
|
||||
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import AutoDiscoveryMappingCache
|
||||
@@ -253,7 +252,6 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
:return:
|
||||
"""
|
||||
result = []
|
||||
db.session.commit()
|
||||
rules = cls.cls.get_by(to_dict=True)
|
||||
|
||||
for rule in rules:
|
||||
@@ -720,12 +718,6 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
|
||||
build_relations_for_ad_accept.apply_async(args=(adc.to_dict(), ci_id, ad_key2attr), queue=CMDB_QUEUE)
|
||||
|
||||
ci_type = CITypeCache.get(adc.type_id)
|
||||
if ci_type and ci_type.name in NET_DEVICE_NAMES and 'ports' in adc.instance:
|
||||
from api.tasks.cmdb import add_net_device_ports
|
||||
add_net_device_ports.apply_async(args=(ci_id, adc.instance['ports']),
|
||||
queue=CMDB_QUEUE)
|
||||
|
||||
adc.update(is_accept=True,
|
||||
accept_by=nickname or current_user.nickname,
|
||||
accept_time=datetime.datetime.now(),
|
||||
|
@@ -4,8 +4,6 @@ from api.lib.cmdb.const import AutoDiscoveryType
|
||||
|
||||
PRIVILEGED_USERS = ("cmdb_agent", "worker", "admin")
|
||||
|
||||
NET_DEVICE_NAMES = {"switch", 'router', 'firewall', 'printer'}
|
||||
|
||||
DEFAULT_INNER = [
|
||||
dict(name="阿里云", en="aliyun", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-aliyun'}, "en": "aliyun"}),
|
||||
@@ -43,12 +41,8 @@ DEFAULT_INNER = [
|
||||
option={'icon': {'name': 'caise-luyouqi'}}),
|
||||
dict(name="防火墙", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-fanghuoqiang'}}),
|
||||
# dict(name="打印机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
# option={'icon': {'name': 'caise-dayinji'}}),
|
||||
dict(name="光纤交换机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-fiber'}}),
|
||||
dict(name="F5", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-F5'}}),
|
||||
dict(name="打印机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-dayinji'}}),
|
||||
]
|
||||
|
||||
CLOUD_MAP = {
|
||||
|
@@ -1,74 +1,37 @@
|
||||
[
|
||||
{
|
||||
"name": "manufacturer",
|
||||
"type": "文本",
|
||||
"example": "Huawei",
|
||||
"desc": "制造产商"
|
||||
},
|
||||
{
|
||||
"name": "sn",
|
||||
"type": "文本",
|
||||
"example": "102030059898",
|
||||
"desc": "设备序列号"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "文本",
|
||||
"example": "USG6525E",
|
||||
"desc": "设备名称"
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"type": "文本",
|
||||
"example": "2011.2.321.1.205",
|
||||
"desc": "设备细分类型 结合相关产商获取相应的产品类型"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "文本",
|
||||
"example": "Huawei Vwersatile Routing Platform Software",
|
||||
"desc": "设备描述"
|
||||
},
|
||||
{
|
||||
"name": "manager_ip",
|
||||
"type": "文本",
|
||||
"example": "192.168.1.1",
|
||||
"desc": "管理ip"
|
||||
},
|
||||
{
|
||||
"name": "ips",
|
||||
"type": "文本、多值",
|
||||
"example": "192.168.1.1, 192.168.1.2",
|
||||
"desc": "ips"
|
||||
},
|
||||
{
|
||||
"name": "uptime",
|
||||
"type": "文本",
|
||||
"example": "2023-04-15 10:00:00",
|
||||
"desc": "启动时间"
|
||||
},
|
||||
{
|
||||
"name": "snmp_version",
|
||||
"type": "文本",
|
||||
"example": "v2c",
|
||||
"desc": "SNMP版本"
|
||||
},
|
||||
{
|
||||
"name": "port_num",
|
||||
"type": "整数",
|
||||
"example": 24,
|
||||
"desc": "端口数量"
|
||||
},
|
||||
{
|
||||
"name": "ports",
|
||||
"type": "json",
|
||||
"example": "",
|
||||
"desc": "设备的端口列表"
|
||||
},
|
||||
{
|
||||
"name": "neighbors",
|
||||
"type": "json",
|
||||
"example": "",
|
||||
"desc": "设备的邻居列表"
|
||||
}
|
||||
[{
|
||||
"name":"manufacturer",
|
||||
"type": "文本",
|
||||
"example":"HUAWEI Technology Co.,Ltd",
|
||||
"desc":"制造产商"
|
||||
},{
|
||||
"name":"sn",
|
||||
"type": "文本",
|
||||
"example":"102030059898",
|
||||
"desc":"设备序列号"
|
||||
},{
|
||||
"name":"device_name",
|
||||
"type": "文本",
|
||||
"example":"USG6525E",
|
||||
"desc":"设备名称"
|
||||
},{
|
||||
"name":"device_model",
|
||||
"type": "文本",
|
||||
"example":"2011.2.321.1.205",
|
||||
"desc":"设备细分类型 结合相关产商获取相应的产品类型"
|
||||
},{
|
||||
"name":"description",
|
||||
"type": "文本",
|
||||
"example":"Huawei Vwersatile Routing Platform Software",
|
||||
"desc":"设备描述"
|
||||
},{
|
||||
"name":"manager_ip",
|
||||
"type": "文本",
|
||||
"example":"192.168.1.1",
|
||||
"desc":"管理ip"
|
||||
}, {
|
||||
"name":"ips",
|
||||
"type": "文本、多值",
|
||||
"example":"192.168.1.1, 192.168.1.2",
|
||||
"desc":"ips"
|
||||
}
|
||||
]
|
@@ -1399,7 +1399,6 @@ class CITypeTemplateManager(object):
|
||||
i.pop('order', None)
|
||||
i.pop('choice_web_hook', None)
|
||||
i.pop('choice_other', None)
|
||||
i.pop('choice_builtin', None)
|
||||
i.pop('order', None)
|
||||
i.pop('inherited_from', None)
|
||||
choice_value = i.pop('choice_value', None)
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import importlib.util
|
||||
import imp
|
||||
|
||||
import copy
|
||||
import jinja2
|
||||
@@ -180,15 +180,14 @@ class AttributeValueManager(object):
|
||||
|
||||
@staticmethod
|
||||
def _compute_attr_value_from_expr(expr, ci_dict):
|
||||
t = jinja2.Template(expr).render(ci_dict)
|
||||
|
||||
try:
|
||||
result = jinja2.Template(expr).render(ci_dict)
|
||||
return result
|
||||
return eval(t)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(
|
||||
f"Expression evaluation error - Expression: '{expr}'"
|
||||
f"Input parameters: {ci_dict}, Error type: {type(e).__name__}, Error message: {str(e)}"
|
||||
)
|
||||
return None
|
||||
current_app.logger.warning(str(e))
|
||||
return t
|
||||
|
||||
@staticmethod
|
||||
def _compute_attr_value_from_script(script, ci_dict):
|
||||
script = jinja2.Template(script).render(ci_dict)
|
||||
@@ -199,11 +198,11 @@ class AttributeValueManager(object):
|
||||
|
||||
try:
|
||||
path = script_f.name
|
||||
name = os.path.basename(path)[:-3]
|
||||
dir_name, name = os.path.dirname(path), os.path.basename(path)[:-3]
|
||||
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
fp, path, desc = imp.find_module(name, [dir_name])
|
||||
|
||||
mod = imp.load_module(name, fp, path, desc)
|
||||
|
||||
if hasattr(mod, 'computed'):
|
||||
return mod.computed()
|
||||
|
@@ -376,29 +376,6 @@ def build_relations_for_ad_accept(adc, ci_id, ad_key2attr):
|
||||
pass
|
||||
|
||||
|
||||
@celery.task(name="cmdb.add_net_device_ports", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def add_net_device_ports(ci_id, ports):
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
|
||||
port_type = CITypeCache.get("net_port")
|
||||
if not port_type:
|
||||
current_app.logger.warning("CIType net port is not found")
|
||||
return
|
||||
|
||||
for port in ports:
|
||||
try:
|
||||
port_id = CIManager.add(port_type.id, is_auto_discovery=True, _is_admin=True, **port)
|
||||
|
||||
CIRelationManager.add(ci_id, port_id,
|
||||
valid=False,
|
||||
source=RelationSourceEnum.AUTO_DISCOVERY)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("add_net_device_ports failed: {}".format(e))
|
||||
|
||||
|
||||
@celery.task(name="cmdb.dcim_calc_u_free_count", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def dcim_calc_u_free_count():
|
||||
|
@@ -1,37 +0,0 @@
|
||||
import os
|
||||
|
||||
from api.resource import APIView
|
||||
from api.lib.perm.auth import auth_abandoned
|
||||
|
||||
prefix = "/system"
|
||||
|
||||
|
||||
class SystemLanguageView(APIView):
|
||||
url_prefix = (f"{prefix}/language",)
|
||||
|
||||
method_decorators = []
|
||||
|
||||
@auth_abandoned
|
||||
def get(self):
|
||||
"""Get system default language
|
||||
Read from environment variable SYSTEM_DEFAULT_LANGUAGE, default to Chinese if not set
|
||||
"""
|
||||
default_language = os.environ.get("SYSTEM_DEFAULT_LANGUAGE", "")
|
||||
|
||||
return self.jsonify(
|
||||
{
|
||||
"language": default_language,
|
||||
"language_name": self._get_language_name(default_language),
|
||||
}
|
||||
)
|
||||
|
||||
def _get_language_name(self, language_code):
|
||||
"""Return language name based on language code"""
|
||||
language_mapping = {
|
||||
"zh-CN": "中文(简体)",
|
||||
"zh-TW": "中文(繁体)",
|
||||
"en-US": "English",
|
||||
"ja-JP": "日本語",
|
||||
"ko-KR": "한국어",
|
||||
}
|
||||
return language_mapping.get(language_code, "")
|
@@ -54,150 +54,6 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">onterm-symbolic_link</div>
|
||||
<div class="code-name">&#xea23;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">oneterm-batch_execution</div>
|
||||
<div class="code-name">&#xea20;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">oneterm-file_log-selected</div>
|
||||
<div class="code-name">&#xea21;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">oneterm-file_log</div>
|
||||
<div class="code-name">&#xea22;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">file</div>
|
||||
<div class="code-name">&#xea1f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">folder</div>
|
||||
<div class="code-name">&#xea1e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">mongoDB (1)</div>
|
||||
<div class="code-name">&#xea1b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">postgreSQL (1)</div>
|
||||
<div class="code-name">&#xea1c;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">telnet (1)</div>
|
||||
<div class="code-name">&#xea1d;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">command_interception (1)</div>
|
||||
<div class="code-name">&#xea17;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">quick_commands</div>
|
||||
<div class="code-name">&#xea18;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">terminal_settings</div>
|
||||
<div class="code-name">&#xea19;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">basic_settings</div>
|
||||
<div class="code-name">&#xea1a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">asset_management</div>
|
||||
<div class="code-name">&#xea16;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai-seek</div>
|
||||
<div class="code-name">&#xea15;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai-hate1</div>
|
||||
<div class="code-name">&#xea13;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai-like1</div>
|
||||
<div class="code-name">&#xea14;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai-like2</div>
|
||||
<div class="code-name">&#xea11;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai-hate2</div>
|
||||
<div class="code-name">&#xea12;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai-top_up</div>
|
||||
<div class="code-name">&#xea10;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai-top_down</div>
|
||||
<div class="code-name">&#xea0f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">autoflow-script</div>
|
||||
<div class="code-name">&#xea0d;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">autoflow-dag</div>
|
||||
<div class="code-name">&#xea0e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-default_line</div>
|
||||
<div class="code-name">&#xea0c;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-servicetree</div>
|
||||
@@ -6354,9 +6210,9 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1749393321370') format('woff2'),
|
||||
url('iconfont.woff?t=1749393321370') format('woff'),
|
||||
url('iconfont.ttf?t=1749393321370') format('truetype');
|
||||
src: url('iconfont.woff2?t=1735191938771') format('woff2'),
|
||||
url('iconfont.woff?t=1735191938771') format('woff'),
|
||||
url('iconfont.ttf?t=1735191938771') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@@ -6382,222 +6238,6 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont onterm-symbolic_link"></span>
|
||||
<div class="name">
|
||||
onterm-symbolic_link
|
||||
</div>
|
||||
<div class="code-name">.onterm-symbolic_link
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont oneterm-batch_execution"></span>
|
||||
<div class="name">
|
||||
oneterm-batch_execution
|
||||
</div>
|
||||
<div class="code-name">.oneterm-batch_execution
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ops-oneterm-file_log-selected"></span>
|
||||
<div class="name">
|
||||
oneterm-file_log-selected
|
||||
</div>
|
||||
<div class="code-name">.ops-oneterm-file_log-selected
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ops-oneterm-file_log"></span>
|
||||
<div class="name">
|
||||
oneterm-file_log
|
||||
</div>
|
||||
<div class="code-name">.ops-oneterm-file_log
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont file"></span>
|
||||
<div class="name">
|
||||
file
|
||||
</div>
|
||||
<div class="code-name">.file
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont folder1"></span>
|
||||
<div class="name">
|
||||
folder
|
||||
</div>
|
||||
<div class="code-name">.folder1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-mongoDB1"></span>
|
||||
<div class="name">
|
||||
mongoDB (1)
|
||||
</div>
|
||||
<div class="code-name">.a-mongoDB1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-postgreSQL1"></span>
|
||||
<div class="name">
|
||||
postgreSQL (1)
|
||||
</div>
|
||||
<div class="code-name">.a-postgreSQL1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-telnet1"></span>
|
||||
<div class="name">
|
||||
telnet (1)
|
||||
</div>
|
||||
<div class="code-name">.a-telnet1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-command_interception1"></span>
|
||||
<div class="name">
|
||||
command_interception (1)
|
||||
</div>
|
||||
<div class="code-name">.a-command_interception1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont quick_commands"></span>
|
||||
<div class="name">
|
||||
quick_commands
|
||||
</div>
|
||||
<div class="code-name">.quick_commands
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont terminal_settings"></span>
|
||||
<div class="name">
|
||||
terminal_settings
|
||||
</div>
|
||||
<div class="code-name">.terminal_settings
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont basic_settings"></span>
|
||||
<div class="name">
|
||||
basic_settings
|
||||
</div>
|
||||
<div class="code-name">.basic_settings
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ops-oneterm-asset-management"></span>
|
||||
<div class="name">
|
||||
asset_management
|
||||
</div>
|
||||
<div class="code-name">.ops-oneterm-asset-management
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ai-seek"></span>
|
||||
<div class="name">
|
||||
ai-seek
|
||||
</div>
|
||||
<div class="code-name">.ai-seek
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ai-hate1"></span>
|
||||
<div class="name">
|
||||
ai-hate1
|
||||
</div>
|
||||
<div class="code-name">.ai-hate1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ai-like1"></span>
|
||||
<div class="name">
|
||||
ai-like1
|
||||
</div>
|
||||
<div class="code-name">.ai-like1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ai-like2"></span>
|
||||
<div class="name">
|
||||
ai-like2
|
||||
</div>
|
||||
<div class="code-name">.ai-like2
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ai-hate2"></span>
|
||||
<div class="name">
|
||||
ai-hate2
|
||||
</div>
|
||||
<div class="code-name">.ai-hate2
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ai-top_up"></span>
|
||||
<div class="name">
|
||||
ai-top_up
|
||||
</div>
|
||||
<div class="code-name">.ai-top_up
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ai-top_down"></span>
|
||||
<div class="name">
|
||||
ai-top_down
|
||||
</div>
|
||||
<div class="code-name">.ai-top_down
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont autoflow-script"></span>
|
||||
<div class="name">
|
||||
autoflow-script
|
||||
</div>
|
||||
<div class="code-name">.autoflow-script
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont autoflow-dag"></span>
|
||||
<div class="name">
|
||||
autoflow-dag
|
||||
</div>
|
||||
<div class="code-name">.autoflow-dag
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-default_line"></span>
|
||||
<div class="name">
|
||||
itsm-default_line
|
||||
</div>
|
||||
<div class="code-name">.itsm-default_line
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-servicetree"></span>
|
||||
<div class="name">
|
||||
@@ -15832,198 +15472,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="#onterm-symbolic_link"></use>
|
||||
</svg>
|
||||
<div class="name">onterm-symbolic_link</div>
|
||||
<div class="code-name">#onterm-symbolic_link</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#oneterm-batch_execution"></use>
|
||||
</svg>
|
||||
<div class="name">oneterm-batch_execution</div>
|
||||
<div class="code-name">#oneterm-batch_execution</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ops-oneterm-file_log-selected"></use>
|
||||
</svg>
|
||||
<div class="name">oneterm-file_log-selected</div>
|
||||
<div class="code-name">#ops-oneterm-file_log-selected</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ops-oneterm-file_log"></use>
|
||||
</svg>
|
||||
<div class="name">oneterm-file_log</div>
|
||||
<div class="code-name">#ops-oneterm-file_log</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#file"></use>
|
||||
</svg>
|
||||
<div class="name">file</div>
|
||||
<div class="code-name">#file</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#folder1"></use>
|
||||
</svg>
|
||||
<div class="name">folder</div>
|
||||
<div class="code-name">#folder1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-mongoDB1"></use>
|
||||
</svg>
|
||||
<div class="name">mongoDB (1)</div>
|
||||
<div class="code-name">#a-mongoDB1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-postgreSQL1"></use>
|
||||
</svg>
|
||||
<div class="name">postgreSQL (1)</div>
|
||||
<div class="code-name">#a-postgreSQL1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-telnet1"></use>
|
||||
</svg>
|
||||
<div class="name">telnet (1)</div>
|
||||
<div class="code-name">#a-telnet1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-command_interception1"></use>
|
||||
</svg>
|
||||
<div class="name">command_interception (1)</div>
|
||||
<div class="code-name">#a-command_interception1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#quick_commands"></use>
|
||||
</svg>
|
||||
<div class="name">quick_commands</div>
|
||||
<div class="code-name">#quick_commands</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#terminal_settings"></use>
|
||||
</svg>
|
||||
<div class="name">terminal_settings</div>
|
||||
<div class="code-name">#terminal_settings</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#basic_settings"></use>
|
||||
</svg>
|
||||
<div class="name">basic_settings</div>
|
||||
<div class="code-name">#basic_settings</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ops-oneterm-asset-management"></use>
|
||||
</svg>
|
||||
<div class="name">asset_management</div>
|
||||
<div class="code-name">#ops-oneterm-asset-management</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ai-seek"></use>
|
||||
</svg>
|
||||
<div class="name">ai-seek</div>
|
||||
<div class="code-name">#ai-seek</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ai-hate1"></use>
|
||||
</svg>
|
||||
<div class="name">ai-hate1</div>
|
||||
<div class="code-name">#ai-hate1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ai-like1"></use>
|
||||
</svg>
|
||||
<div class="name">ai-like1</div>
|
||||
<div class="code-name">#ai-like1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ai-like2"></use>
|
||||
</svg>
|
||||
<div class="name">ai-like2</div>
|
||||
<div class="code-name">#ai-like2</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ai-hate2"></use>
|
||||
</svg>
|
||||
<div class="name">ai-hate2</div>
|
||||
<div class="code-name">#ai-hate2</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ai-top_up"></use>
|
||||
</svg>
|
||||
<div class="name">ai-top_up</div>
|
||||
<div class="code-name">#ai-top_up</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ai-top_down"></use>
|
||||
</svg>
|
||||
<div class="name">ai-top_down</div>
|
||||
<div class="code-name">#ai-top_down</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#autoflow-script"></use>
|
||||
</svg>
|
||||
<div class="name">autoflow-script</div>
|
||||
<div class="code-name">#autoflow-script</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#autoflow-dag"></use>
|
||||
</svg>
|
||||
<div class="name">autoflow-dag</div>
|
||||
<div class="code-name">#autoflow-dag</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-default_line"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-default_line</div>
|
||||
<div class="code-name">#itsm-default_line</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-servicetree"></use>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1749393321370') format('woff2'),
|
||||
url('iconfont.woff?t=1749393321370') format('woff'),
|
||||
url('iconfont.ttf?t=1749393321370') format('truetype');
|
||||
src: url('iconfont.woff2?t=1735191938771') format('woff2'),
|
||||
url('iconfont.woff?t=1735191938771') format('woff'),
|
||||
url('iconfont.ttf?t=1735191938771') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,102 +13,6 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.onterm-symbolic_link:before {
|
||||
content: "\ea23";
|
||||
}
|
||||
|
||||
.oneterm-batch_execution:before {
|
||||
content: "\ea20";
|
||||
}
|
||||
|
||||
.ops-oneterm-file_log-selected:before {
|
||||
content: "\ea21";
|
||||
}
|
||||
|
||||
.ops-oneterm-file_log:before {
|
||||
content: "\ea22";
|
||||
}
|
||||
|
||||
.file:before {
|
||||
content: "\ea1f";
|
||||
}
|
||||
|
||||
.folder1:before {
|
||||
content: "\ea1e";
|
||||
}
|
||||
|
||||
.a-mongoDB1:before {
|
||||
content: "\ea1b";
|
||||
}
|
||||
|
||||
.a-postgreSQL1:before {
|
||||
content: "\ea1c";
|
||||
}
|
||||
|
||||
.a-telnet1:before {
|
||||
content: "\ea1d";
|
||||
}
|
||||
|
||||
.a-command_interception1:before {
|
||||
content: "\ea17";
|
||||
}
|
||||
|
||||
.quick_commands:before {
|
||||
content: "\ea18";
|
||||
}
|
||||
|
||||
.terminal_settings:before {
|
||||
content: "\ea19";
|
||||
}
|
||||
|
||||
.basic_settings:before {
|
||||
content: "\ea1a";
|
||||
}
|
||||
|
||||
.ops-oneterm-asset-management:before {
|
||||
content: "\ea16";
|
||||
}
|
||||
|
||||
.ai-seek:before {
|
||||
content: "\ea15";
|
||||
}
|
||||
|
||||
.ai-hate1:before {
|
||||
content: "\ea13";
|
||||
}
|
||||
|
||||
.ai-like1:before {
|
||||
content: "\ea14";
|
||||
}
|
||||
|
||||
.ai-like2:before {
|
||||
content: "\ea11";
|
||||
}
|
||||
|
||||
.ai-hate2:before {
|
||||
content: "\ea12";
|
||||
}
|
||||
|
||||
.ai-top_up:before {
|
||||
content: "\ea10";
|
||||
}
|
||||
|
||||
.ai-top_down:before {
|
||||
content: "\ea0f";
|
||||
}
|
||||
|
||||
.autoflow-script:before {
|
||||
content: "\ea0d";
|
||||
}
|
||||
|
||||
.autoflow-dag:before {
|
||||
content: "\ea0e";
|
||||
}
|
||||
|
||||
.itsm-default_line:before {
|
||||
content: "\ea0c";
|
||||
}
|
||||
|
||||
.veops-servicetree:before {
|
||||
content: "\ea0b";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,174 +5,6 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "44501032",
|
||||
"name": "onterm-symbolic_link",
|
||||
"font_class": "onterm-symbolic_link",
|
||||
"unicode": "ea23",
|
||||
"unicode_decimal": 59939
|
||||
},
|
||||
{
|
||||
"icon_id": "44497221",
|
||||
"name": "oneterm-batch_execution",
|
||||
"font_class": "oneterm-batch_execution",
|
||||
"unicode": "ea20",
|
||||
"unicode_decimal": 59936
|
||||
},
|
||||
{
|
||||
"icon_id": "44497220",
|
||||
"name": "oneterm-file_log-selected",
|
||||
"font_class": "ops-oneterm-file_log-selected",
|
||||
"unicode": "ea21",
|
||||
"unicode_decimal": 59937
|
||||
},
|
||||
{
|
||||
"icon_id": "44497219",
|
||||
"name": "oneterm-file_log",
|
||||
"font_class": "ops-oneterm-file_log",
|
||||
"unicode": "ea22",
|
||||
"unicode_decimal": 59938
|
||||
},
|
||||
{
|
||||
"icon_id": "44455092",
|
||||
"name": "file",
|
||||
"font_class": "file",
|
||||
"unicode": "ea1f",
|
||||
"unicode_decimal": 59935
|
||||
},
|
||||
{
|
||||
"icon_id": "44455100",
|
||||
"name": "folder",
|
||||
"font_class": "folder1",
|
||||
"unicode": "ea1e",
|
||||
"unicode_decimal": 59934
|
||||
},
|
||||
{
|
||||
"icon_id": "44315758",
|
||||
"name": "mongoDB (1)",
|
||||
"font_class": "a-mongoDB1",
|
||||
"unicode": "ea1b",
|
||||
"unicode_decimal": 59931
|
||||
},
|
||||
{
|
||||
"icon_id": "44315757",
|
||||
"name": "postgreSQL (1)",
|
||||
"font_class": "a-postgreSQL1",
|
||||
"unicode": "ea1c",
|
||||
"unicode_decimal": 59932
|
||||
},
|
||||
{
|
||||
"icon_id": "44315755",
|
||||
"name": "telnet (1)",
|
||||
"font_class": "a-telnet1",
|
||||
"unicode": "ea1d",
|
||||
"unicode_decimal": 59933
|
||||
},
|
||||
{
|
||||
"icon_id": "44276353",
|
||||
"name": "command_interception (1)",
|
||||
"font_class": "a-command_interception1",
|
||||
"unicode": "ea17",
|
||||
"unicode_decimal": 59927
|
||||
},
|
||||
{
|
||||
"icon_id": "44276352",
|
||||
"name": "quick_commands",
|
||||
"font_class": "quick_commands",
|
||||
"unicode": "ea18",
|
||||
"unicode_decimal": 59928
|
||||
},
|
||||
{
|
||||
"icon_id": "44276351",
|
||||
"name": "terminal_settings",
|
||||
"font_class": "terminal_settings",
|
||||
"unicode": "ea19",
|
||||
"unicode_decimal": 59929
|
||||
},
|
||||
{
|
||||
"icon_id": "44276350",
|
||||
"name": "basic_settings",
|
||||
"font_class": "basic_settings",
|
||||
"unicode": "ea1a",
|
||||
"unicode_decimal": 59930
|
||||
},
|
||||
{
|
||||
"icon_id": "44276278",
|
||||
"name": "asset_management",
|
||||
"font_class": "ops-oneterm-asset-management",
|
||||
"unicode": "ea16",
|
||||
"unicode_decimal": 59926
|
||||
},
|
||||
{
|
||||
"icon_id": "43267802",
|
||||
"name": "ai-seek",
|
||||
"font_class": "ai-seek",
|
||||
"unicode": "ea15",
|
||||
"unicode_decimal": 59925
|
||||
},
|
||||
{
|
||||
"icon_id": "43213714",
|
||||
"name": "ai-hate1",
|
||||
"font_class": "ai-hate1",
|
||||
"unicode": "ea13",
|
||||
"unicode_decimal": 59923
|
||||
},
|
||||
{
|
||||
"icon_id": "43213712",
|
||||
"name": "ai-like1",
|
||||
"font_class": "ai-like1",
|
||||
"unicode": "ea14",
|
||||
"unicode_decimal": 59924
|
||||
},
|
||||
{
|
||||
"icon_id": "43213717",
|
||||
"name": "ai-like2",
|
||||
"font_class": "ai-like2",
|
||||
"unicode": "ea11",
|
||||
"unicode_decimal": 59921
|
||||
},
|
||||
{
|
||||
"icon_id": "43213716",
|
||||
"name": "ai-hate2",
|
||||
"font_class": "ai-hate2",
|
||||
"unicode": "ea12",
|
||||
"unicode_decimal": 59922
|
||||
},
|
||||
{
|
||||
"icon_id": "43139007",
|
||||
"name": "ai-top_up",
|
||||
"font_class": "ai-top_up",
|
||||
"unicode": "ea10",
|
||||
"unicode_decimal": 59920
|
||||
},
|
||||
{
|
||||
"icon_id": "43139017",
|
||||
"name": "ai-top_down",
|
||||
"font_class": "ai-top_down",
|
||||
"unicode": "ea0f",
|
||||
"unicode_decimal": 59919
|
||||
},
|
||||
{
|
||||
"icon_id": "43029539",
|
||||
"name": "autoflow-script",
|
||||
"font_class": "autoflow-script",
|
||||
"unicode": "ea0d",
|
||||
"unicode_decimal": 59917
|
||||
},
|
||||
{
|
||||
"icon_id": "43029538",
|
||||
"name": "autoflow-dag",
|
||||
"font_class": "autoflow-dag",
|
||||
"unicode": "ea0e",
|
||||
"unicode_decimal": 59918
|
||||
},
|
||||
{
|
||||
"icon_id": "42960865",
|
||||
"name": "itsm-default_line",
|
||||
"font_class": "itsm-default_line",
|
||||
"unicode": "ea0c",
|
||||
"unicode_decimal": 59916
|
||||
},
|
||||
{
|
||||
"icon_id": "42930714",
|
||||
"name": "veops-servicetree",
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,7 +12,6 @@ import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
|
||||
import enUS from 'ant-design-vue/lib/locale-provider/en_US'
|
||||
import { AppDeviceEnquire } from '@/utils/mixin'
|
||||
import { debounce } from './utils/util'
|
||||
import { getSystemLanguage } from '@/api/system.js'
|
||||
|
||||
import { h } from 'snabbdom'
|
||||
import { DomEditor, Boot } from '@wangeditor/editor'
|
||||
@@ -46,7 +45,8 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initLanguage()
|
||||
this.SET_LOCALE(localStorage.getItem('ops_locale') || 'zh')
|
||||
this.$i18n.locale = localStorage.getItem('ops_locale') || 'zh'
|
||||
this.timer = setInterval(() => {
|
||||
this.setTime(new Date().getTime())
|
||||
}, 1000)
|
||||
@@ -200,28 +200,6 @@ export default {
|
||||
this.alive = true
|
||||
})
|
||||
},
|
||||
async initLanguage() {
|
||||
let saveLocale = localStorage.getItem('ops_locale')
|
||||
if (!saveLocale) {
|
||||
let requestLanguage = ''
|
||||
try {
|
||||
const languageRes = await getSystemLanguage()
|
||||
requestLanguage = languageRes?.language || ''
|
||||
} catch (e) {
|
||||
console.error('getSystemLanguage error:', e)
|
||||
}
|
||||
|
||||
// request language variable || user local system language
|
||||
const userLanguage = requestLanguage || navigator.language || navigator.userLanguage
|
||||
if (userLanguage.includes('zh')) {
|
||||
saveLocale = 'zh'
|
||||
} else {
|
||||
saveLocale = 'en'
|
||||
}
|
||||
}
|
||||
this.SET_LOCALE(saveLocale)
|
||||
this.$i18n.locale = saveLocale
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,8 +0,0 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getSystemLanguage() {
|
||||
return axios({
|
||||
url: '/common-setting/v1/system/language',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
@@ -47,7 +47,7 @@ export const commonIconList = ['changyong-ubuntu',
|
||||
export const linearIconList = [
|
||||
{
|
||||
value: 'database',
|
||||
label: 'components.database',
|
||||
label: '数据库',
|
||||
list: [{
|
||||
value: 'icon-xianxing-DB2',
|
||||
label: 'DB2'
|
||||
@@ -81,7 +81,7 @@ export const linearIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'system',
|
||||
label: 'components.system',
|
||||
label: '操作系统',
|
||||
list: [{
|
||||
value: 'icon-xianxing-Windows',
|
||||
label: 'Windows'
|
||||
@@ -106,7 +106,7 @@ export const linearIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'language',
|
||||
label: 'components.language',
|
||||
label: '语言',
|
||||
list: [{
|
||||
value: 'icon-xianxing-python',
|
||||
label: 'python'
|
||||
@@ -137,7 +137,7 @@ export const linearIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'status',
|
||||
label: 'components.status',
|
||||
label: '状态',
|
||||
list: [{
|
||||
value: 'icon-xianxing-yiwen',
|
||||
label: '疑问'
|
||||
@@ -177,7 +177,7 @@ export const linearIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'icon-xianxing-application',
|
||||
label: 'components.commonComponent',
|
||||
label: '常用组件',
|
||||
list: [{
|
||||
value: 'icon-xianxing-yilianjie',
|
||||
label: '已连接'
|
||||
@@ -310,7 +310,7 @@ export const linearIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'data',
|
||||
label: 'components.data',
|
||||
label: '数据',
|
||||
list: [{
|
||||
value: 'icon-xianxing-bingzhuangtu',
|
||||
label: '饼状图'
|
||||
@@ -387,7 +387,7 @@ export const linearIconList = [
|
||||
export const fillIconList = [
|
||||
{
|
||||
value: 'database',
|
||||
label: 'components.database',
|
||||
label: '数据库',
|
||||
list: [{
|
||||
value: 'icon-shidi-DB2',
|
||||
label: 'DB2'
|
||||
@@ -421,7 +421,7 @@ export const fillIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'system',
|
||||
label: 'components.system',
|
||||
label: '操作系统',
|
||||
list: [{
|
||||
value: 'icon-shidi-Windows',
|
||||
label: 'Windows'
|
||||
@@ -446,7 +446,7 @@ export const fillIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'language',
|
||||
label: 'components.language',
|
||||
label: '语言',
|
||||
list: [{
|
||||
value: 'icon-shidi-python',
|
||||
label: 'python'
|
||||
@@ -477,7 +477,7 @@ export const fillIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'status',
|
||||
label: 'components.status',
|
||||
label: '状态',
|
||||
list: [{
|
||||
value: 'icon-shidi-yiwen',
|
||||
label: '疑问'
|
||||
@@ -517,7 +517,7 @@ export const fillIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'icon-shidi-application',
|
||||
label: 'components.commonComponent',
|
||||
label: '常用组件',
|
||||
list: [{
|
||||
value: 'icon-shidi-yilianjie',
|
||||
label: '已连接'
|
||||
@@ -650,7 +650,7 @@ export const fillIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'data',
|
||||
label: 'components.data',
|
||||
label: '数据',
|
||||
list: [{
|
||||
value: 'icon-shidi-bingzhuangtu',
|
||||
label: '饼状图'
|
||||
@@ -727,7 +727,7 @@ export const fillIconList = [
|
||||
export const multicolorIconList = [
|
||||
{
|
||||
value: 'database',
|
||||
label: 'components.database',
|
||||
label: '数据库',
|
||||
list: [{
|
||||
value: 'caise-TIDB',
|
||||
label: 'TIDB'
|
||||
@@ -773,7 +773,7 @@ export const multicolorIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'cloud',
|
||||
label: 'components.cloud',
|
||||
label: '云',
|
||||
list: [{
|
||||
value: 'AWS',
|
||||
label: 'AWS'
|
||||
@@ -819,7 +819,7 @@ export const multicolorIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'system',
|
||||
label: 'components.system',
|
||||
label: '操作系统',
|
||||
list: [{
|
||||
value: 'ciase-aix',
|
||||
label: 'aix'
|
||||
@@ -847,7 +847,7 @@ export const multicolorIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'language',
|
||||
label: 'components.language',
|
||||
label: '语言',
|
||||
list: [{
|
||||
value: 'caise-python',
|
||||
label: 'python'
|
||||
@@ -878,7 +878,7 @@ export const multicolorIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'status',
|
||||
label: 'components.status',
|
||||
label: '状态',
|
||||
list: [{
|
||||
value: 'caise-yiwen',
|
||||
label: '疑问'
|
||||
@@ -918,7 +918,7 @@ export const multicolorIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'caise-application',
|
||||
label: 'components.commonComponent',
|
||||
label: '常用组件',
|
||||
list: [{
|
||||
value: 'caise-websphere',
|
||||
label: 'WebSphere'
|
||||
@@ -1180,7 +1180,7 @@ export const multicolorIconList = [
|
||||
}]
|
||||
}, {
|
||||
value: 'data',
|
||||
label: 'components.data',
|
||||
label: '数据',
|
||||
list: [{
|
||||
value: 'caise-bingzhuangtu',
|
||||
label: '饼状图'
|
||||
|
@@ -33,7 +33,7 @@
|
||||
<template v-if="iconList && iconList.length">
|
||||
<template v-if="currentIconType !== '4'">
|
||||
<div v-for="category in iconList" :key="category.value">
|
||||
<h4 class="category">{{ $t(category.label) }}</h4>
|
||||
<h4 class="category">{{ category.label }}</h4>
|
||||
<div class="custom-icon-select-popover-content-wrapper">
|
||||
<div
|
||||
v-for="name in category.list"
|
||||
|
@@ -6,9 +6,9 @@
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>{{ config[type].title }}</h1>
|
||||
<div class="desc">{{ $t(config[type].desc) }}</div>
|
||||
<div class="desc">{{ config[type].desc }}</div>
|
||||
<div class="actions">
|
||||
<a-button type="primary" @click="handleToHome">{{ $t('exception.backToHome') }}</a-button>
|
||||
<a-button type="primary" @click="handleToHome">返回首页</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -2,17 +2,17 @@ const types = {
|
||||
403: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
|
||||
title: '403',
|
||||
desc: 'exception.desc1'
|
||||
desc: '抱歉,你无权访问该页面'
|
||||
},
|
||||
404: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
|
||||
title: '404',
|
||||
desc: 'exception.desc2'
|
||||
desc: '抱歉,你访问的页面不存在或仍在开发中'
|
||||
},
|
||||
500: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
|
||||
title: '500',
|
||||
desc: 'exception.desc3'
|
||||
desc: '抱歉,服务器出错了'
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -109,25 +109,9 @@ export default {
|
||||
default: 'default',
|
||||
tip: 'Tip',
|
||||
cmdbSearch: 'Search',
|
||||
exception: {
|
||||
backToHome: 'Back to home page',
|
||||
desc1: 'Sorry, you are not authorized to access this page',
|
||||
desc2: 'Sorry, the page you are visiting does not exist or is still under development',
|
||||
desc3: 'Sorry, server error'
|
||||
},
|
||||
pagination: {
|
||||
total: '{range0}-{range1} of {total} items'
|
||||
},
|
||||
components: {
|
||||
colorTagSelectTip: 'Enter or select tags',
|
||||
database: 'Database',
|
||||
system: 'System',
|
||||
language: 'Language',
|
||||
status: 'Status',
|
||||
commonComponent: 'Common Component',
|
||||
data: 'Data',
|
||||
cloud: 'Cloud'
|
||||
},
|
||||
topMenu: {
|
||||
personalCenter: 'My Profile',
|
||||
logout: 'Logout',
|
||||
|
@@ -109,25 +109,9 @@ export default {
|
||||
default: '默认',
|
||||
tip: '提示',
|
||||
cmdbSearch: '搜索一下',
|
||||
exception: {
|
||||
backToHome: '返回首页',
|
||||
desc1: '抱歉,你无权访问该页面',
|
||||
desc2: '抱歉,你访问的页面不存在或仍在开发中',
|
||||
desc3: '抱歉,服务器出错了'
|
||||
},
|
||||
pagination: {
|
||||
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
|
||||
},
|
||||
components: {
|
||||
colorTagSelectTip: '选择或输入(回车确定)标签',
|
||||
database: '数据库',
|
||||
system: '操作系统',
|
||||
language: '语言',
|
||||
status: '状态',
|
||||
commonComponent: '常用组件',
|
||||
data: '数据',
|
||||
cloud: '云'
|
||||
},
|
||||
topMenu: {
|
||||
personalCenter: '个人中心',
|
||||
logout: '退出登录',
|
||||
|
@@ -132,7 +132,6 @@ export default {
|
||||
/deep/ .ant-select-selection {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
border: none;
|
||||
|
||||
.ant-select-selection__rendered {
|
||||
height: 28px;
|
||||
|
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
<span
|
||||
class="ci-icon-letter"
|
||||
v-else-if="title"
|
||||
v-else
|
||||
>
|
||||
<span>
|
||||
{{ title[0].toUpperCase() }}
|
||||
|
147
cmdb-ui/src/modules/cmdb/components/nodeSetting/index.vue
Normal file
147
cmdb-ui/src/modules/cmdb/components/nodeSetting/index.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="node-setting-wrap">
|
||||
<ops-table
|
||||
:data="nodes"
|
||||
size="mini"
|
||||
show-header-overflow
|
||||
:row-config="{ height: 42 }"
|
||||
border
|
||||
:min-height="78"
|
||||
>
|
||||
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingIp')">
|
||||
<template #default="{ row }">
|
||||
<a-input v-model="row.ip"></a-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingCommunity')">
|
||||
<template #default="{ row }">
|
||||
<a-input v-model="row.community"></a-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingVersion')">
|
||||
<template #default="{ row }">
|
||||
<a-select
|
||||
v-model="row.version"
|
||||
:placeholder="$t('cmdb.ciType.nodeSettingVersionTip')"
|
||||
allowClear
|
||||
class="node-setting-select"
|
||||
>
|
||||
<a-select-option value="1">
|
||||
v1
|
||||
</a-select-option>
|
||||
<a-select-option value="2c">
|
||||
v2c
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column wdith="170">
|
||||
<template #default="{ row }">
|
||||
<div class="action">
|
||||
<a @click="() => copyNode(row.id)">
|
||||
<a-icon type="copy" />
|
||||
</a>
|
||||
<a @click="() => removeNode(row.id, 1)">
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a @click="addNode">
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</ops-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'MonitorNodeSetting',
|
||||
props: {
|
||||
initNodes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodes: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initNodesFunc() {
|
||||
this.nodes = _.cloneDeep(this.initNodes)
|
||||
},
|
||||
addNode() {
|
||||
const newNode = {
|
||||
id: uuidv4(),
|
||||
ip: '',
|
||||
community: 'public',
|
||||
version: '',
|
||||
}
|
||||
this.nodes.push(newNode)
|
||||
},
|
||||
removeNode(removeId, minLength) {
|
||||
if (this.nodes.length <= minLength) {
|
||||
this.$message.error('不可再删除!')
|
||||
return
|
||||
}
|
||||
const _idx = this.nodes.findIndex((item) => item.id === removeId)
|
||||
if (_idx > -1) {
|
||||
this.nodes.splice(_idx, 1)
|
||||
}
|
||||
},
|
||||
copyNode(id) {
|
||||
const copyNode = this.nodes.find((item) => item.id === id)
|
||||
if (copyNode) {
|
||||
const newNode = {
|
||||
...copyNode,
|
||||
id: uuidv4(),
|
||||
}
|
||||
this.nodes.push(newNode)
|
||||
}
|
||||
},
|
||||
getNodeValue() {
|
||||
const nodes = this.nodes.map((node) => {
|
||||
return _.pick(node, ['ip', 'community', 'version'])
|
||||
})
|
||||
return nodes
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.node-setting-wrap {
|
||||
margin-left: 17px;
|
||||
width: 600px;
|
||||
|
||||
.ant-row {
|
||||
/deep/ .ant-input-clear-icon {
|
||||
color: rgba(0,0,0,.25);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-setting-select {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
@@ -77,7 +77,6 @@ const cmdb_en = {
|
||||
confirmDeleteADT: 'Do you confirm to delete [{pluginName}]',
|
||||
attributeMap: 'Attribute mapping',
|
||||
nodeConfig: 'Node Configuration',
|
||||
scanningParameter: 'Scanning Parameter',
|
||||
autoDiscovery: 'AutoDiscovery',
|
||||
node: 'Node',
|
||||
adExecConfig: 'Execute configuration',
|
||||
@@ -254,25 +253,6 @@ const cmdb_en = {
|
||||
checkModalColumn4: 'Last checkup time',
|
||||
testModalTitle: 'Automated discovery testing',
|
||||
attrMapTableAttrPlaceholder: 'Please edit the name',
|
||||
SNMPConfiguration: 'SNMP Configuration',
|
||||
nodeList: 'Node List',
|
||||
defaultVersion: 'Default Version',
|
||||
defaultCommunity: 'Default Community',
|
||||
timeout: 'Timeout',
|
||||
retryCount: 'Retry Count',
|
||||
scanningConfiguration: 'Scanning Configuration',
|
||||
initialNode: 'Initial Node',
|
||||
defaultGateway: 'Default Gateway',
|
||||
recursiveOrNot: 'Recursive Or Not',
|
||||
recursiveTip: 'Scanning Configuration: When disabling recursion, the node list must be configured.',
|
||||
maximumDepth: 'Maximum Depth',
|
||||
snmpFormTip1: 'If SNMP is not the default, Community and version need to be configured separately',
|
||||
snmpFormTip2: 'Timeout for establishing SNMP connection',
|
||||
snmpFormTip3: 'Number of retries to establish an SNMP connection',
|
||||
snmpFormTip4: 'The first node to start scanning, or recursively from the default gateway if unconfigured',
|
||||
snmpFormTip5: 'Enabled by default to discover all network devices and topology relationships as much as possible, and disabled to scan only the devices in the node list',
|
||||
snmpFormTip6: 'Depth of network device topology',
|
||||
snmpFormTip7: 'The results of the scan are filtered with CIDR, not filtered if not configured. Format: 192.168.1.0/24',
|
||||
nodeSettingIp: 'Network device IP address',
|
||||
nodeSettingIpTip: 'Please enter the ip address',
|
||||
nodeSettingIpTip1: 'ip address format error',
|
||||
@@ -721,9 +701,6 @@ if __name__ == "__main__":
|
||||
batchRollbacking: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed',
|
||||
baselineTips: 'Changes at this point in time will also be rollbacked, Unique ID, password and dynamic attributes do not support',
|
||||
cover: 'Cover',
|
||||
detail: 'Detail',
|
||||
upstream: 'Upstream',
|
||||
downstream: 'Downstream'
|
||||
},
|
||||
serviceTree: {
|
||||
remove: 'Remove',
|
||||
|
@@ -77,7 +77,6 @@ const cmdb_zh = {
|
||||
confirmDeleteADT: '确认删除 【{pluginName}】',
|
||||
attributeMap: '字段映射',
|
||||
nodeConfig: '节点配置',
|
||||
scanningParameter: '扫描参数',
|
||||
autoDiscovery: '自动发现属性',
|
||||
node: '节点',
|
||||
adExecConfig: '执行配置',
|
||||
@@ -254,25 +253,6 @@ const cmdb_zh = {
|
||||
checkModalColumn4: '最近检查时间',
|
||||
testModalTitle: '自动发现测试',
|
||||
attrMapTableAttrPlaceholder: '请编辑名称',
|
||||
SNMPConfiguration: 'SNMP配置',
|
||||
nodeList: '节点列表',
|
||||
defaultVersion: '默认版本',
|
||||
defaultCommunity: '默认 Community',
|
||||
timeout: '超时时间',
|
||||
retryCount: '重试次数',
|
||||
scanningConfiguration: '扫描配置',
|
||||
initialNode: '初始节点',
|
||||
defaultGateway: '默认网关',
|
||||
recursiveOrNot: '是否递归',
|
||||
recursiveTip: '扫描配置关闭递归时, 必须配置节点列表',
|
||||
maximumDepth: '最大深度',
|
||||
snmpFormTip1: '如果不是默认的SNMP, Community和版本需要单独配置',
|
||||
snmpFormTip2: '建立SNMP连接的超时时间',
|
||||
snmpFormTip3: '建立SNMP连接的重试次数',
|
||||
snmpFormTip4: '开始扫描的第一个节点,如果不配置则是从默认网关开始递归扫描',
|
||||
snmpFormTip5: '默认开启,表示尽可能发现所有网络设备和拓扑关系, 如果关闭,则仅扫描节点列表里的设备',
|
||||
snmpFormTip6: '网络设备拓扑的深度',
|
||||
snmpFormTip7: '扫描的结果用CIDR进行过滤,不配置则不会过滤。格式: 192.168.1.0/24',
|
||||
nodeSettingIp: '网络设备IP地址',
|
||||
nodeSettingIpTip: '请输入 ip 地址',
|
||||
nodeSettingIpTip1: 'ip地址格式错误',
|
||||
@@ -720,9 +700,6 @@ if __name__ == "__main__":
|
||||
batchRollbacking: '正在回滚,共{total}个,成功{successNum}个,失败{errorNum}个',
|
||||
baselineTips: '该时间点的变更也会被回滚, 唯一标识、密码属性、动态属性不支持回滚',
|
||||
cover: '覆盖',
|
||||
detail: '详情',
|
||||
upstream: '上游',
|
||||
downstream: '下游'
|
||||
},
|
||||
serviceTree: {
|
||||
remove: '移除',
|
||||
|
@@ -71,7 +71,7 @@ const genCmdbRoutes = async () => {
|
||||
{
|
||||
path: '/cmdb/adc',
|
||||
name: 'cmdb_auto_discovery_ci',
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc', keepAlive: false, permission: ['admin', 'cmdb_admin'] },
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc', keepAlive: false },
|
||||
component: () => import('../views/discoveryCI/index.vue')
|
||||
},
|
||||
{
|
||||
|
@@ -17,15 +17,7 @@
|
||||
:ci_id="ci._id"
|
||||
:attr_id="attr.id"
|
||||
></PasswordField>
|
||||
<a-tooltip
|
||||
v-else-if="attr.value_type === '6'"
|
||||
:title="JSON.stringify(ci[attr.name] || {})"
|
||||
overlayClassName="ci-detail-attr-json-tooltip"
|
||||
>
|
||||
<span class="ci-detail-attr-json">
|
||||
{{ JSON.stringify(ci[attr.name] || {}) }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<template v-else-if="attr.value_type === '6'">{{ JSON.stringify(ci[attr.name] || {}) }}</template>
|
||||
<template v-else-if="attr.is_choice">
|
||||
<template v-if="attr.is_list">
|
||||
<span
|
||||
@@ -356,22 +348,4 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-detail-attr-json {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.ci-detail-attr-json-tooltip {
|
||||
|
||||
.ant-tooltip-content {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<div class="ci-detail-table-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CIDetailTableTitle',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-detail-table-title {
|
||||
height: 42px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: @text-color_1;
|
||||
padding: 0px 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #EBF0F9;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
height: 44px;
|
||||
width: 300px;
|
||||
background: #F8F9FD60;
|
||||
transform: rotate(40deg);
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
height: 44px;
|
||||
width: 300px;
|
||||
background: #F8F9FD60;
|
||||
transform: rotate(40deg);
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: calc(25% + 100px);
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div class="ci-detail-title">
|
||||
<CIIcon :icon="icon" size="20" />
|
||||
<span class="ci-detail-title-text">{{ title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CIIcon from '@/modules/cmdb/components/ciIcon'
|
||||
|
||||
export default {
|
||||
name: 'CIDetailTitle',
|
||||
components: {
|
||||
CIIcon
|
||||
},
|
||||
props: {
|
||||
ci: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
ci_types: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icon: '',
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
findCIType() {
|
||||
return this.ci_types?.find?.((item) => item?.id === this.ci?._type) || {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
findCIType: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.icon = val?.icon || ''
|
||||
this.title = this?.ci?.[val?.show_name] || this?.ci?.[val?.unique_key] || ''
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-detail-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
column-gap: 9px;
|
||||
|
||||
&-text {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: @text-color_1;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,673 +0,0 @@
|
||||
<template>
|
||||
<div v-if="allCITypes.length" class="ci-relation-table">
|
||||
<CIDetailTableTitle :title="$t('cmdb.relation')" />
|
||||
|
||||
<div class="ci-relation-table-wrap">
|
||||
<div class="ci-relation-table-tab">
|
||||
<div
|
||||
v-for="(group) in tabList"
|
||||
:key="group.key"
|
||||
class="tab-group"
|
||||
>
|
||||
<div
|
||||
v-if="group.name"
|
||||
class="tab-group-name"
|
||||
>
|
||||
{{ group.name }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(item) in group.list"
|
||||
:key="item.key"
|
||||
:class="`tab-item ${item.key === currentTab ? 'tab-item-active' : ''}`"
|
||||
:style="{
|
||||
paddingLeft: item.key === 'all' ? '8px' : '16px'
|
||||
}"
|
||||
@click="clickTab(item.key)"
|
||||
>
|
||||
<span class="tab-item-name">
|
||||
<a-tooltip :title="item.name">
|
||||
<span class="tab-item-name-text">{{ item.name }}</span>
|
||||
</a-tooltip>
|
||||
<span
|
||||
v-if="item.count"
|
||||
class="tab-item-name-count"
|
||||
>
|
||||
({{ item.count }})
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="item.key === currentTab && item.showAdd"
|
||||
class="tab-item-add"
|
||||
@click="openAddModal(item)"
|
||||
>
|
||||
<a-icon type="plus" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="ci-relation-table-container"
|
||||
v-if="tableIDList.length"
|
||||
>
|
||||
<div
|
||||
v-for="(item) in tableIDList"
|
||||
:key="item.key"
|
||||
class="ci-relation-table-item"
|
||||
>
|
||||
<div
|
||||
v-if="currentTab === 'all'"
|
||||
class="ci-relation-table-item-name"
|
||||
>
|
||||
<span class="ci-relation-table-item-name-text">{{ item.name }}</span>
|
||||
<span class="ci-relation-table-item-name-count">({{ item.count }})</span>
|
||||
</div>
|
||||
|
||||
<vxe-grid
|
||||
bordered
|
||||
size="mini"
|
||||
:columns="allColumns[item.value]"
|
||||
:data="allCIList[item.key]"
|
||||
overflow
|
||||
showOverflow="tooltip"
|
||||
showHeaderOverflow="tooltip"
|
||||
resizable
|
||||
class="ops-stripe-table"
|
||||
max-height="300px"
|
||||
>
|
||||
<template #reference_default="{ row, column }">
|
||||
<a
|
||||
v-for="(id) in (column.params.attr.is_list ? row[column.field] : [row[column.field]])"
|
||||
:key="id"
|
||||
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ getReferenceName(id, column) }}
|
||||
</a>
|
||||
</template>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm
|
||||
arrowPointAtCenter
|
||||
:title="$t('cmdb.ci.confirmDeleteRelation')"
|
||||
@confirm="deleteRelation(row)"
|
||||
>
|
||||
<a
|
||||
:disabled="!allCanEdit[item.value]"
|
||||
:style="{
|
||||
color: !allCanEdit[item.value] ? 'rgba(0, 0, 0, 0.25)' : 'red',
|
||||
}"
|
||||
>
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AddTableModal ref="addTableModal" @reload="refreshTableData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { deleteCIRelationView } from '@/modules/cmdb/api/CIRelation'
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
|
||||
import CIDetailTableTitle from './ciDetailTableTitle.vue'
|
||||
import AddTableModal from '@/modules/cmdb/views/relation_views/modules/AddTableModal.vue'
|
||||
|
||||
const PARENT_KEY = 'parents'
|
||||
const CHILDREN_KEY = 'children'
|
||||
|
||||
export default {
|
||||
name: 'CIRelationTable',
|
||||
components: {
|
||||
CIDetailTableTitle,
|
||||
AddTableModal
|
||||
},
|
||||
inject: {
|
||||
ci_types: { from: 'ci_types' },
|
||||
relationViewRefreshNumber: {
|
||||
from: 'relationViewRefreshNumber',
|
||||
default: () => null,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
ciId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
typeId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
ci: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
relationData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tabList: [],
|
||||
currentTab: 'all',
|
||||
allCITypes: [],
|
||||
allColumns: {},
|
||||
allJSONAttr: {},
|
||||
allCIList: {},
|
||||
allCanEdit: {},
|
||||
referenceCINameMap: {}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tabListFlat() {
|
||||
return this.tabList.reduce((list, group) => list.concat(group.list), [])
|
||||
},
|
||||
tableIDList() {
|
||||
const baseKeys = this.currentTab === 'all'
|
||||
? this.tabListFlat.filter(item => item.value !== 'all').map(item => item.key)
|
||||
: [this.currentTab]
|
||||
|
||||
return baseKeys.filter((key) => this.allCIList?.[key]?.length).map((key) => {
|
||||
const findTab = this.tabListFlat.find((item) => item.key === key) || {}
|
||||
|
||||
let name = findTab?.name || ''
|
||||
if (name && findTab?.value === this.ci._type) {
|
||||
name = `${findTab?.isParent ? this.$t('cmdb.ci.upstream') : this.$t('cmdb.ci.downstream')} - ${name}`
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
value: findTab?.value || '',
|
||||
name,
|
||||
count: findTab?.count || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
relationData: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.init(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init(relationData) {
|
||||
const ci_types_list = this.ci_types()
|
||||
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
|
||||
if (!_findCiType) {
|
||||
return
|
||||
}
|
||||
|
||||
const cloneRelationData = _.cloneDeep(relationData)
|
||||
|
||||
const allCITypes = _.uniqBy(
|
||||
[
|
||||
...cloneRelationData.parentCITypeList,
|
||||
...cloneRelationData.childCITypeList
|
||||
],
|
||||
'id'
|
||||
)
|
||||
await this.handleSubscribeAttributes(allCITypes)
|
||||
|
||||
const {
|
||||
columns: parentColumns,
|
||||
jsonAttr: parentJSONAttr,
|
||||
} = this.handleCITypeList(cloneRelationData.parentCITypeList, true)
|
||||
const {
|
||||
columns: childColumns,
|
||||
jsonAttr: childJSONAttr,
|
||||
} = this.handleCITypeList(cloneRelationData.childCITypeList, false)
|
||||
|
||||
this.allCITypes = allCITypes
|
||||
this.allColumns = {
|
||||
...parentColumns,
|
||||
...childColumns
|
||||
}
|
||||
this.allJSONAttr = {
|
||||
...parentJSONAttr,
|
||||
...childJSONAttr
|
||||
}
|
||||
|
||||
await this.getCanEditList(this.allCITypes)
|
||||
|
||||
const [parentCIs, childCIs] = await Promise.all([
|
||||
this.handleCIList(cloneRelationData.parentCIList, true),
|
||||
this.handleCIList(cloneRelationData.childCIList, false)
|
||||
])
|
||||
this.allCIList = {
|
||||
...parentCIs,
|
||||
...childCIs
|
||||
}
|
||||
|
||||
const tabList = []
|
||||
|
||||
tabList[0] = {
|
||||
name: '',
|
||||
key: 'all',
|
||||
list: [{
|
||||
name: this.$t('all'),
|
||||
key: 'all',
|
||||
value: 'all',
|
||||
count: Object.values(this.allCIList).reduce((acc, cur) => acc + (cur?.length || 0), 0),
|
||||
showAdd: false
|
||||
}]
|
||||
}
|
||||
tabList[1] = {
|
||||
name: this.$t('cmdb.ci.upstream'),
|
||||
key: PARENT_KEY,
|
||||
list: this.buildTabList(cloneRelationData.parentCITypeList, PARENT_KEY, true)
|
||||
}
|
||||
tabList[2] = {
|
||||
name: this.$t('cmdb.ci.downstream'),
|
||||
key: CHILDREN_KEY,
|
||||
list: this.buildTabList(cloneRelationData.childCITypeList, CHILDREN_KEY, false)
|
||||
}
|
||||
this.tabList = tabList
|
||||
|
||||
this.handleReferenceCINameMap()
|
||||
},
|
||||
|
||||
buildTabList(list, keyPrefix, isParent) {
|
||||
return list.map((item) => {
|
||||
const key = `${keyPrefix}-${item.id}`
|
||||
return {
|
||||
name: item?.alias ?? item?.name ?? '',
|
||||
key,
|
||||
isParent,
|
||||
value: item.id,
|
||||
count: this.allCIList?.[key]?.length || 0,
|
||||
showAdd: this.allCanEdit?.[item.id] ?? false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleCITypeList(list, isParent) {
|
||||
const CIColumns = {}
|
||||
const CIJSONAttr = {}
|
||||
|
||||
list.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
|
||||
item.isParent = isParent
|
||||
item.attributes.forEach((attr) => {
|
||||
const column = {
|
||||
key: 'p_' + attr.id,
|
||||
field: attr.name,
|
||||
title: attr.alias,
|
||||
minWidth: '100px',
|
||||
params: {
|
||||
attr
|
||||
},
|
||||
}
|
||||
if (attr.is_reference) {
|
||||
column.slots = {
|
||||
default: 'reference_default'
|
||||
}
|
||||
}
|
||||
columns.push(column)
|
||||
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
CIJSONAttr[item.id] = jsonAttr
|
||||
CIColumns[item.id] = columns
|
||||
CIColumns[item.id].push({
|
||||
key: 'p_operation',
|
||||
field: 'operation',
|
||||
title: this.$t('operation'),
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
columns: CIColumns,
|
||||
jsonAttr: CIJSONAttr
|
||||
}
|
||||
},
|
||||
|
||||
async getCanEditList(allCITypes) {
|
||||
const promises = allCITypes.map((ciType) => {
|
||||
let parentId = ciType.id
|
||||
let childId = this.typeId
|
||||
|
||||
if (!ciType.isParent) {
|
||||
parentId = this.typeId
|
||||
childId = ciType.id
|
||||
}
|
||||
|
||||
return getCanEditByParentIdChildId(parentId, childId).then((res) => {
|
||||
return { id: ciType.id, canEdit: res.result }
|
||||
})
|
||||
})
|
||||
|
||||
const allCanEdit = {}
|
||||
|
||||
const res = await Promise.all(promises)
|
||||
if (res?.length) {
|
||||
res.map((item) => {
|
||||
allCanEdit[item.id] = item.canEdit
|
||||
})
|
||||
}
|
||||
|
||||
this.allCanEdit = allCanEdit
|
||||
},
|
||||
|
||||
async handleSubscribeAttributes(allCITypes) {
|
||||
const promises = allCITypes.map((ciType, index) => {
|
||||
return getSubscribeAttributes(ciType.id).then((res) => {
|
||||
return {
|
||||
...(res || {}),
|
||||
id: ciType.id,
|
||||
indexInAll: index
|
||||
}
|
||||
})
|
||||
})
|
||||
const res = await Promise.all(promises)
|
||||
|
||||
if (res?.length) {
|
||||
res.forEach((item) => {
|
||||
if (
|
||||
allCITypes?.[item.indexInAll]?.attributes &&
|
||||
item?.is_subscribed
|
||||
) {
|
||||
allCITypes[item.indexInAll].attributes = item.attributes
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return allCITypes
|
||||
},
|
||||
|
||||
async handleCIList(ciList, isParent) {
|
||||
const cis = {}
|
||||
ciList.forEach((item) => {
|
||||
this.allJSONAttr[item._type].forEach((attr) => {
|
||||
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
|
||||
})
|
||||
this.formatCI(item)
|
||||
item.isParent = isParent
|
||||
const CIKey = `${isParent ? PARENT_KEY : CHILDREN_KEY}-${item._type}`
|
||||
|
||||
if (CIKey in cis) {
|
||||
cis[CIKey].push(item)
|
||||
} else {
|
||||
cis[CIKey] = [item]
|
||||
}
|
||||
})
|
||||
|
||||
return cis
|
||||
},
|
||||
|
||||
formatCI(ci) {
|
||||
Object.keys(ci).forEach((key) => {
|
||||
const attr = this.allColumns?.[ci?._type]?.find((item) => item?.params?.attr?.name === key)?.params?.attr
|
||||
if (attr?.is_choice && attr?.choice_value?.length) {
|
||||
if (attr?.is_list) {
|
||||
ci[key] = ci[key].map((value) => {
|
||||
const label = attr?.choice_value?.find((choice) => choice?.[0] === value)?.[1]?.label
|
||||
return label || ci[key]
|
||||
})
|
||||
} else {
|
||||
const label = attr?.choice_value?.find((choice) => choice?.[0] === ci[key])?.[1]?.label
|
||||
ci[key] = label || ci[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return ci
|
||||
},
|
||||
|
||||
async handleReferenceCINameMap() {
|
||||
const referenceCINameMap = {}
|
||||
this.allCITypes.forEach((CIType) => {
|
||||
const CIKey = `${CIType.isParent ? PARENT_KEY : CHILDREN_KEY}-${CIType.id}`
|
||||
|
||||
CIType.attributes.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id) {
|
||||
const currentCIList = this.allCIList[CIKey]
|
||||
if (currentCIList?.length) {
|
||||
currentCIList.forEach((ci) => {
|
||||
const ids = Array.isArray(ci[attr.name]) ? ci[attr.name] : ci[attr.name] ? [ci[attr.name]] : []
|
||||
|
||||
if (ids.length) {
|
||||
if (!referenceCINameMap?.[attr.reference_type_id]) {
|
||||
referenceCINameMap[attr.reference_type_id] = {}
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
referenceCINameMap[attr.reference_type_id][id] = ''
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!Object.keys(referenceCINameMap).length) {
|
||||
return
|
||||
}
|
||||
|
||||
const allRes = await Promise.all(
|
||||
Object.keys(referenceCINameMap).map((key) => {
|
||||
return searchCI({
|
||||
q: `_type:${key},_id:(${Object.keys(referenceCINameMap[key]).join(';')})`,
|
||||
count: 9999
|
||||
})
|
||||
})
|
||||
)
|
||||
const CITypeList = this.ci_types()
|
||||
const showNameMap = {}
|
||||
|
||||
Object.keys(referenceCINameMap).forEach((id) => {
|
||||
const CIType = CITypeList.find((CIType) => Number(CIType.id) === Number(id))
|
||||
|
||||
showNameMap[id] = {
|
||||
show_name: CIType?.show_name,
|
||||
unique_key: CIType?.unique_key
|
||||
}
|
||||
})
|
||||
|
||||
allRes.forEach((res) => {
|
||||
res.result.forEach((item) => {
|
||||
if (referenceCINameMap?.[item._type]?.[item._id] === '') {
|
||||
const showName = showNameMap?.[item._type]
|
||||
|
||||
referenceCINameMap[item._type][item._id] = item?.[showName?.show_name] ?? item?.[showName?.unique_key] ?? ''
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.referenceCINameMap = referenceCINameMap
|
||||
},
|
||||
|
||||
getReferenceName(id, column) {
|
||||
const typeId = column?.params?.attr?.reference_type_id
|
||||
return this.referenceCINameMap?.[typeId]?.[id] || id
|
||||
},
|
||||
|
||||
clickTab(key) {
|
||||
this.currentTab = key
|
||||
},
|
||||
|
||||
deleteRelation(row) {
|
||||
const first_ci_id = row?.isParent ? row?._id : this.ciId
|
||||
const second_ci_id = row?.isParent ? this.ciId : row?._id
|
||||
|
||||
deleteCIRelationView(first_ci_id, second_ci_id).then(() => {
|
||||
this.refreshTableData()
|
||||
if (this.relationViewRefreshNumber) {
|
||||
this.relationViewRefreshNumber()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
openAddModal(tabData) {
|
||||
const ciType = this.allCITypes.find((item) => item.id === tabData.value)
|
||||
|
||||
this.$refs.addTableModal.openModal(
|
||||
{
|
||||
[`${this.ci.unique}`]: this.ci?.[this.ci.unique]
|
||||
},
|
||||
this.ciId,
|
||||
ciType,
|
||||
tabData?.isParent ? 'parents' : 'children'
|
||||
)
|
||||
},
|
||||
|
||||
async refreshTableData() {
|
||||
this.$emit('refreshRelationCI')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-relation-table {
|
||||
width: 100%;
|
||||
margin-top: 32px;
|
||||
|
||||
&-wrap {
|
||||
border: solid 1px #E4E7ED;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-tab {
|
||||
flex-shrink: 0;
|
||||
width: 160px;
|
||||
min-height: 300px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 6px 0px;
|
||||
border-right: solid 1px #E4E7ED;
|
||||
|
||||
.tab-group {
|
||||
width: 100%;
|
||||
|
||||
&-name {
|
||||
padding-left: 8px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-left: 16px;
|
||||
padding-right: 10px;
|
||||
background-color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
|
||||
&-name {
|
||||
font-size: 14px;
|
||||
color: @text-color_1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
max-width: calc(100% - 16px);
|
||||
|
||||
&-text {
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
color: @text-color_2;
|
||||
}
|
||||
|
||||
&-count {
|
||||
color: @text-color_3;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-add {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 14px;
|
||||
background-color: #FFFFFF;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: @primary-color;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-active {
|
||||
background-color: #F0F5FF;
|
||||
|
||||
.tab-item-name-text {
|
||||
color: @text-color_1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.tab-item-name-text {
|
||||
color: @text-color_1;
|
||||
}
|
||||
|
||||
.tab-item-add {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-container {
|
||||
width: 100%;
|
||||
padding: 15px 17px;
|
||||
overflow: hidden;
|
||||
min-height: 300px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&-name {
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: @text-color_1;
|
||||
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
&-count {
|
||||
font-size: 12px;
|
||||
color: @text-color_3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,95 +0,0 @@
|
||||
import { getCITypeChildren, getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { searchCIRelation } from '@/modules/cmdb/api/CIRelation'
|
||||
|
||||
const RelationMixin = {
|
||||
data() {
|
||||
return {
|
||||
relationData: {
|
||||
parentCITypeList: [],
|
||||
childCITypeList: [],
|
||||
parentCIList: [],
|
||||
childCIList: []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async initRelationData(typeId, ciId) {
|
||||
const {
|
||||
parentCITypeList,
|
||||
childCITypeList
|
||||
} = await this.getRelationCITypeList(typeId)
|
||||
const {
|
||||
parentCIList,
|
||||
childCIList
|
||||
} = await this.getRelationCIList(ciId)
|
||||
this.relationData = {
|
||||
parentCITypeList,
|
||||
childCITypeList,
|
||||
parentCIList,
|
||||
childCIList
|
||||
}
|
||||
},
|
||||
|
||||
async getRelationCITypeList(typeId) {
|
||||
let parentCITypeList = []
|
||||
let childCITypeList = []
|
||||
|
||||
if (typeId) {
|
||||
parentCITypeList = await this.getParentCITypeList(typeId)
|
||||
childCITypeList = await this.getChildCITypeList(typeId)
|
||||
}
|
||||
|
||||
return {
|
||||
parentCITypeList,
|
||||
childCITypeList
|
||||
}
|
||||
},
|
||||
|
||||
async getRelationCIList(ciId) {
|
||||
let parentCIList = []
|
||||
let childCIList = []
|
||||
|
||||
if (ciId) {
|
||||
parentCIList = await this.getParentCIList(ciId)
|
||||
childCIList = await this.getChildCIList(ciId)
|
||||
}
|
||||
|
||||
return {
|
||||
parentCIList,
|
||||
childCIList
|
||||
}
|
||||
},
|
||||
|
||||
async refreshRelationCI(ciId) {
|
||||
const {
|
||||
parentCIList,
|
||||
childCIList
|
||||
} = await this.getRelationCIList(ciId)
|
||||
this.relationData.parentCIList = parentCIList
|
||||
this.relationData.childCIList = childCIList
|
||||
},
|
||||
|
||||
async getParentCITypeList(typeId) {
|
||||
const res = await getCITypeParent(typeId)
|
||||
return res?.parents || []
|
||||
},
|
||||
|
||||
async getChildCITypeList(typeId) {
|
||||
const res = await getCITypeChildren(typeId)
|
||||
return res.children || []
|
||||
},
|
||||
|
||||
async getParentCIList(ciId) {
|
||||
const res = await searchCIRelation(`root_id=${ciId}&level=1&reverse=1&count=10000`)
|
||||
return res?.result || []
|
||||
},
|
||||
|
||||
async getChildCIList(ciId) {
|
||||
const res = await searchCIRelation(`root_id=${ciId}&level=1&reverse=0&count=10000`)
|
||||
return res?.result || []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RelationMixin
|
@@ -1,16 +1,146 @@
|
||||
<template>
|
||||
<div class="ci-detail-relation">
|
||||
<CiDetailRelationTopo ref="ciDetailRelationTopo"/>
|
||||
<a-radio-group v-model="activeKey" size="small" @change="handleChangeActiveKey">
|
||||
<a-radio-button value="1">
|
||||
{{ $t('cmdb.ci.topo') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button value="2">
|
||||
{{ $t('cmdb.ci.table') }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
<CiDetailRelationTopo ref="ciDetailRelationTopo" v-if="activeKey === '1'" />
|
||||
<template v-if="activeKey === '2'">
|
||||
<template v-for="parent in parentCITypes">
|
||||
<div :key="'ctr_' + parent.ctr_id">
|
||||
<div class="ci-detail-relation-table-title">
|
||||
{{ parent.alias || parent.name }}
|
||||
<a
|
||||
:disabled="!canEdit[parent.id]"
|
||||
@click="
|
||||
() => {
|
||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent, 'parents')
|
||||
}
|
||||
"
|
||||
><a-icon
|
||||
type="plus-square"
|
||||
/></a>
|
||||
<span v-if="!canEdit[parent.id]">({{ $t('cmdb.ci.m2mTips') }})</span>
|
||||
</div>
|
||||
<vxe-grid
|
||||
v-if="firstCIs[parent.name]"
|
||||
bordered
|
||||
size="mini"
|
||||
:columns="firstCIColumns[parent.id]"
|
||||
:data="firstCIs[parent.name]"
|
||||
overflow
|
||||
showOverflow="tooltip"
|
||||
showHeaderOverflow="tooltip"
|
||||
resizable
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<template #reference_default="{ row, column }">
|
||||
<a
|
||||
v-for="(id) in (column.params.attr.is_list ? row[column.field] : [row[column.field]])"
|
||||
:key="id"
|
||||
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ getReferenceName(id, column) }}
|
||||
</a>
|
||||
</template>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm
|
||||
arrowPointAtCenter
|
||||
:title="$t('cmdb.ci.confirmDeleteRelation')"
|
||||
@confirm="deleteRelation(row._id, ciId)"
|
||||
>
|
||||
<a
|
||||
:disabled="!canEdit[parent.id]"
|
||||
:style="{
|
||||
color: !canEdit[parent.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
|
||||
}"
|
||||
><a-icon
|
||||
type="delete"
|
||||
/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</div>
|
||||
</template>
|
||||
<a-divider />
|
||||
<template v-for="child in childCITypes">
|
||||
<div :key="'ctr_' + child.ctr_id">
|
||||
<div class="ci-detail-relation-table-title">
|
||||
{{ child.alias || child.name }}
|
||||
<a
|
||||
:disabled="!canEdit[child.id]"
|
||||
@click="
|
||||
() => {
|
||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child, 'children')
|
||||
}
|
||||
"
|
||||
><a-icon
|
||||
type="plus-square"
|
||||
/></a>
|
||||
<span v-if="!canEdit[child.id]">({{ $t('cmdb.ci.m2mTips') }})</span>
|
||||
</div>
|
||||
<vxe-grid
|
||||
v-if="secondCIs[child.name]"
|
||||
bordered
|
||||
size="mini"
|
||||
:columns="secondCIColumns[child.id]"
|
||||
:data="secondCIs[child.name]"
|
||||
showOverflow="tooltip"
|
||||
showHeaderOverflow="tooltip"
|
||||
resizable
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<template #reference_default="{ row, column }">
|
||||
<a
|
||||
v-for="(id) in (column.params.attr.is_list ? row[column.field] : [row[column.field]])"
|
||||
:key="id"
|
||||
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ getReferenceName(id, column) }}
|
||||
</a>
|
||||
</template>
|
||||
<template #operation_default="{ row }">
|
||||
<a-popconfirm
|
||||
arrowPointAtCenter
|
||||
:title="$t('cmdb.ci.confirmDeleteRelation')"
|
||||
@confirm="deleteRelation(ciId, row._id)"
|
||||
>
|
||||
<a
|
||||
:disabled="!canEdit[child.id]"
|
||||
:style="{
|
||||
color: !canEdit[child.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
|
||||
}"
|
||||
><a-icon
|
||||
type="delete"
|
||||
/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<AddTableModal ref="addTableModal" @reload="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getCITypeChildren, getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { searchCIRelation, deleteCIRelationView } from '@/modules/cmdb/api/CIRelation'
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailRelationTopo from './ciDetailRelationTopo/index.vue'
|
||||
import Node from './ciDetailRelationTopo/node.js'
|
||||
import AddTableModal from '../../relation_views/modules/AddTableModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'CIDetailRelation',
|
||||
components: { CiDetailRelationTopo },
|
||||
name: 'CiDetailRelation',
|
||||
components: { CiDetailRelationTopo, AddTableModal },
|
||||
props: {
|
||||
ciId: {
|
||||
type: Number,
|
||||
@@ -24,32 +154,41 @@ export default {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
relationData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
initQueryLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeKey: '1',
|
||||
parentCITypes: [],
|
||||
childCITypes: [],
|
||||
firstCIs: {},
|
||||
firstCIColumns: {},
|
||||
secondCIs: {},
|
||||
secondCIColumns: {},
|
||||
firstCIJsonAttr: {},
|
||||
secondCIJsonAttr: {},
|
||||
canEdit: {},
|
||||
topoData: {
|
||||
nodes: {},
|
||||
edges: []
|
||||
},
|
||||
referenceCINameMap: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
exsited_ci() {
|
||||
const _exsited_ci = [this.ciId]
|
||||
this.relationData.parentCITypeList.forEach((parent) => {
|
||||
this.parentCITypes.forEach((parent) => {
|
||||
if (this.firstCIs[parent.name]) {
|
||||
this.firstCIs[parent.name].forEach((parentCi) => {
|
||||
_exsited_ci.push(parentCi._id)
|
||||
})
|
||||
}
|
||||
})
|
||||
this.relationData.childCITypeList.forEach((child) => {
|
||||
this.childCITypes.forEach((child) => {
|
||||
if (this.secondCIs[child.name]) {
|
||||
this.secondCIs[child.name].forEach((childCi) => {
|
||||
_exsited_ci.push(childCi._id)
|
||||
@@ -68,59 +207,314 @@ export default {
|
||||
default: () => null,
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
relationData: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.init(val)
|
||||
}
|
||||
mounted() {
|
||||
if (!this.initQueryLoading) {
|
||||
this.init(true)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init(relationData) {
|
||||
async init(isFirst) {
|
||||
const ci_types_list = this.ci_types()
|
||||
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
|
||||
if (!_findCiType) {
|
||||
return
|
||||
}
|
||||
|
||||
this.getFirstCIs(relationData.parentCIList)
|
||||
this.getSecondCIs(relationData.childCIList)
|
||||
|
||||
this.handleTopoData()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ciDetailRelationTopo) {
|
||||
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
|
||||
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
|
||||
this.handleTopoData()
|
||||
if (
|
||||
isFirst &&
|
||||
this.$refs.ciDetailRelationTopo &&
|
||||
ci_types_list.length
|
||||
) {
|
||||
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
|
||||
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
|
||||
}
|
||||
|
||||
this.handleReferenceCINameMap()
|
||||
})
|
||||
},
|
||||
async getFirstCIs(parentCIList) {
|
||||
const firstCIs = {}
|
||||
parentCIList.forEach((item) => {
|
||||
if (item.ci_type in firstCIs) {
|
||||
firstCIs[item.ci_type].push(item)
|
||||
} else {
|
||||
firstCIs[item.ci_type] = [item]
|
||||
}
|
||||
})
|
||||
this.firstCIs = firstCIs
|
||||
async getFirstCIs() {
|
||||
await searchCIRelation(`root_id=${Number(this.ciId)}&level=1&reverse=1&count=10000`)
|
||||
.then((res) => {
|
||||
const firstCIs = {}
|
||||
res.result.forEach((item) => {
|
||||
this.firstCIJsonAttr[item._type].forEach((attr) => {
|
||||
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
|
||||
})
|
||||
this.formatCI(item, this.firstCIColumns)
|
||||
if (item.ci_type in firstCIs) {
|
||||
firstCIs[item.ci_type].push(item)
|
||||
} else {
|
||||
firstCIs[item.ci_type] = [item]
|
||||
}
|
||||
})
|
||||
this.firstCIs = firstCIs
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
async getSecondCIs(childCIList) {
|
||||
const secondCIs = {}
|
||||
childCIList.forEach((item) => {
|
||||
if (item.ci_type in secondCIs) {
|
||||
secondCIs[item.ci_type].push(item)
|
||||
} else {
|
||||
secondCIs[item.ci_type] = [item]
|
||||
}
|
||||
})
|
||||
this.secondCIs = secondCIs
|
||||
async getSecondCIs() {
|
||||
await searchCIRelation(`root_id=${Number(this.ciId)}&level=1&reverse=0&count=10000`)
|
||||
.then((res) => {
|
||||
const secondCIs = {}
|
||||
res.result.forEach((item) => {
|
||||
this.secondCIJsonAttr[item._type].forEach((attr) => {
|
||||
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
|
||||
})
|
||||
this.formatCI(item, this.secondCIColumns)
|
||||
if (item.ci_type in secondCIs) {
|
||||
secondCIs[item.ci_type].push(item)
|
||||
} else {
|
||||
secondCIs[item.ci_type] = [item]
|
||||
}
|
||||
})
|
||||
this.secondCIs = secondCIs
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
|
||||
formatCI(ci, columns) {
|
||||
Object.keys(ci).forEach((key) => {
|
||||
const attr = columns?.[ci?._type]?.find((item) => item?.params?.attr?.name === key)?.params?.attr
|
||||
if (attr?.is_choice && attr?.choice_value?.length) {
|
||||
if (attr?.is_list) {
|
||||
ci[key] = ci[key].map((value) => {
|
||||
const label = attr?.choice_value?.find((choice) => choice?.[0] === value)?.[1]?.label
|
||||
return label || ci[key]
|
||||
})
|
||||
} else {
|
||||
const label = attr?.choice_value?.find((choice) => choice?.[0] === ci[key])?.[1]?.label
|
||||
ci[key] = label || ci[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return ci
|
||||
},
|
||||
|
||||
async getParentCITypes() {
|
||||
const res = await getCITypeParent(this.typeId)
|
||||
this.parentCITypes = res.parents
|
||||
for (let i = 0; i < res.parents.length; i++) {
|
||||
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
|
||||
this.canEdit = {
|
||||
..._.cloneDeep(this.canEdit),
|
||||
[res.parents[i].id]: p_res.result,
|
||||
}
|
||||
})
|
||||
}
|
||||
const firstCIColumns = {}
|
||||
const firstCIJsonAttr = {}
|
||||
res.parents.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
const column = {
|
||||
key: 'p_' + attr.id,
|
||||
field: attr.name,
|
||||
title: attr.alias,
|
||||
minWidth: '100px',
|
||||
params: {
|
||||
attr
|
||||
},
|
||||
}
|
||||
if (attr.is_reference) {
|
||||
column.slots = {
|
||||
default: 'reference_default'
|
||||
}
|
||||
}
|
||||
columns.push(column)
|
||||
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
firstCIJsonAttr[item.id] = jsonAttr
|
||||
firstCIColumns[item.id] = columns
|
||||
firstCIColumns[item.id].push({
|
||||
key: 'p_operation',
|
||||
field: 'operation',
|
||||
title: this.$t('operation'),
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.firstCIColumns = firstCIColumns
|
||||
this.firstCIJsonAttr = firstCIJsonAttr
|
||||
},
|
||||
async getChildCITypes() {
|
||||
const res = await getCITypeChildren(this.typeId)
|
||||
|
||||
this.childCITypes = res.children
|
||||
for (let i = 0; i < res.children.length; i++) {
|
||||
await getCanEditByParentIdChildId(this.typeId, res.children[i].id).then((c_res) => {
|
||||
this.canEdit = {
|
||||
..._.cloneDeep(this.canEdit),
|
||||
[res.children[i].id]: c_res.result,
|
||||
}
|
||||
})
|
||||
}
|
||||
const secondCIColumns = {}
|
||||
const secondCIJsonAttr = {}
|
||||
res.children.forEach((item) => {
|
||||
const columns = []
|
||||
const jsonAttr = []
|
||||
item.attributes.forEach((attr) => {
|
||||
const column = {
|
||||
key: 'c_' + attr.id,
|
||||
field: attr.name,
|
||||
title: attr.alias,
|
||||
minWidth: '100px',
|
||||
params: {
|
||||
attr
|
||||
},
|
||||
}
|
||||
if (attr.is_reference) {
|
||||
column.slots = {
|
||||
default: 'reference_default'
|
||||
}
|
||||
}
|
||||
columns.push(column)
|
||||
|
||||
if (attr.value_type === '6') {
|
||||
jsonAttr.push(attr.name)
|
||||
}
|
||||
})
|
||||
secondCIJsonAttr[item.id] = jsonAttr
|
||||
secondCIColumns[item.id] = columns
|
||||
secondCIColumns[item.id].push({
|
||||
key: 'c_operation',
|
||||
field: 'operation',
|
||||
title: this.$t('operation'),
|
||||
width: '60px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'operation_default',
|
||||
},
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
this.secondCIColumns = secondCIColumns
|
||||
this.secondCIJsonAttr = secondCIJsonAttr
|
||||
},
|
||||
|
||||
async handleReferenceCINameMap() {
|
||||
const CITypes = _.unionBy(
|
||||
[
|
||||
...this.parentCITypes,
|
||||
...this.childCITypes
|
||||
],
|
||||
'id'
|
||||
)
|
||||
const CIList = _.unionBy(
|
||||
_.flatten(
|
||||
[
|
||||
...Object.values(this.firstCIs),
|
||||
...Object.values(this.secondCIs)
|
||||
]
|
||||
),
|
||||
'_id'
|
||||
)
|
||||
|
||||
const CIMap = {}
|
||||
CIList.forEach((ci) => {
|
||||
if (!CIMap[ci._type]) {
|
||||
CIMap[ci._type] = []
|
||||
}
|
||||
CIMap[ci._type].push(ci)
|
||||
})
|
||||
|
||||
const referenceCINameMap = {}
|
||||
CITypes.forEach((CIType) => {
|
||||
CIType.attributes.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id) {
|
||||
const currentCIList = CIMap[CIType.id]
|
||||
if (currentCIList?.length) {
|
||||
currentCIList.forEach((ci) => {
|
||||
const ids = Array.isArray(ci[attr.name]) ? ci[attr.name] : ci[attr.name] ? [ci[attr.name]] : []
|
||||
|
||||
if (ids.length) {
|
||||
if (!referenceCINameMap?.[attr.reference_type_id]) {
|
||||
referenceCINameMap[attr.reference_type_id] = {}
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
referenceCINameMap[attr.reference_type_id][id] = ''
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!Object.keys(referenceCINameMap).length) {
|
||||
return
|
||||
}
|
||||
|
||||
const allRes = await Promise.all(
|
||||
Object.keys(referenceCINameMap).map((key) => {
|
||||
return searchCI({
|
||||
q: `_type:${key},_id:(${Object.keys(referenceCINameMap[key]).join(';')})`,
|
||||
count: 9999
|
||||
})
|
||||
})
|
||||
)
|
||||
const CITypeList = this.ci_types()
|
||||
const showNameMap = {}
|
||||
|
||||
Object.keys(referenceCINameMap).forEach((id) => {
|
||||
const CIType = CITypeList.find((CIType) => Number(CIType.id) === Number(id))
|
||||
|
||||
showNameMap[id] = {
|
||||
show_name: CIType?.show_name,
|
||||
unique_key: CIType?.unique_key
|
||||
}
|
||||
})
|
||||
|
||||
allRes.forEach((res) => {
|
||||
res.result.forEach((item) => {
|
||||
if (referenceCINameMap?.[item._type]?.[item._id] === '') {
|
||||
const showName = showNameMap?.[item._type]
|
||||
|
||||
referenceCINameMap[item._type][item._id] = item?.[showName?.show_name] ?? item?.[showName?.unique_key] ?? ''
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.referenceCINameMap = referenceCINameMap
|
||||
},
|
||||
|
||||
getReferenceName(id, column) {
|
||||
const typeId = column?.params?.attr?.reference_type_id
|
||||
return this.referenceCINameMap?.[typeId]?.[id] || id
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.init()
|
||||
},
|
||||
deleteRelation(first_ci_id, second_ci_id) {
|
||||
deleteCIRelationView(first_ci_id, second_ci_id).then((res) => {
|
||||
this.init()
|
||||
if (this.relationViewRefreshNumber) {
|
||||
this.relationViewRefreshNumber()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleChangeActiveKey(e) {
|
||||
if (e.target.value === '1') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
|
||||
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
|
||||
})
|
||||
}
|
||||
},
|
||||
handleTopoData() {
|
||||
const ci_types_list = this.ci_types()
|
||||
if (!ci_types_list?.length) {
|
||||
@@ -132,11 +526,10 @@ export default {
|
||||
}
|
||||
|
||||
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
const unique_id = _findCiType.show_id || this.attributes().unique_id
|
||||
const unique_name = _findCiType.show_name || this.attributes().unique
|
||||
const _findUnique = this.attrList().find((attr) => attr.id === unique_id)
|
||||
const unique_name = _findUnique?.name
|
||||
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
|
||||
|
||||
const nodes = {
|
||||
isRoot: true,
|
||||
id: `Root_${this.typeId}`,
|
||||
@@ -162,7 +555,7 @@ export default {
|
||||
children: [],
|
||||
}
|
||||
const edges = []
|
||||
this.relationData.parentCITypeList.forEach((parent) => {
|
||||
this.parentCITypes.forEach((parent) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
|
||||
if (this.firstCIs[parent.name] && _findCiType) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
@@ -205,7 +598,7 @@ export default {
|
||||
})
|
||||
}
|
||||
})
|
||||
this.relationData.childCITypeList.forEach((child) => {
|
||||
this.childCITypes.forEach((child) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === child.id)
|
||||
if (this.secondCIs[child.name] && _findCiType) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
@@ -260,5 +653,12 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
.ci-detail-relation {
|
||||
height: 100%;
|
||||
.ci-detail-relation-table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 5px;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
id="ci-detail-relation-topo"
|
||||
class="ci-detail-relation-topo"
|
||||
:style="{ width: '100%', height: '100%' }"
|
||||
:style="{ width: '100%', marginTop: '20px', height: 'calc(100% - 44px)' }"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
@@ -25,6 +25,7 @@ export default {
|
||||
}
|
||||
},
|
||||
inject: ['ci_types'],
|
||||
mounted() {},
|
||||
methods: {
|
||||
init() {
|
||||
const root = document.getElementById('ci-detail-relation-topo')
|
||||
|
@@ -6,79 +6,29 @@
|
||||
{{ $t('cmdb.ci.share') }}
|
||||
</a>
|
||||
<a-tab-pane key="tab_1">
|
||||
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.ci.detail') }}</span>
|
||||
|
||||
<div class="ci-detail-table">
|
||||
<CIDetailTitle :ci="ci" :ci_types="ci_types" />
|
||||
|
||||
<div class="ci-detail-table-attr">
|
||||
<CIDetailTableTitle :title="$t('cmdb.attribute')" />
|
||||
|
||||
<div class="ci-detail-table-attr-wrap">
|
||||
<div
|
||||
v-for="group in attributeGroups"
|
||||
:key="group.name"
|
||||
class="ci-detail-table-attr-group"
|
||||
>
|
||||
<div class="ci-detail-table-attr-group-name">
|
||||
{{ group.name || $t('other') }}
|
||||
</div>
|
||||
|
||||
<a-row :gutter="[18, 14]">
|
||||
<a-col
|
||||
v-for="attr in group.attributes"
|
||||
:key="attr.name"
|
||||
:span="8"
|
||||
>
|
||||
<a-row :gutter="[8, 0]">
|
||||
<a-col :span="8">
|
||||
<span class="ci-detail-table-attr-label">
|
||||
<a-tooltip :title="attr.alias || attr.name">
|
||||
<span class="ci-detail-table-attr-label-text">{{ attr.alias || attr.name }}</span>
|
||||
</a-tooltip>
|
||||
<span class="ci-detail-table-attr-label-colon">:</span>
|
||||
</span>
|
||||
</a-col>
|
||||
|
||||
<a-col
|
||||
:span="16"
|
||||
class="ci-detail-table-attr-content"
|
||||
>
|
||||
<CIDetailAttrContent
|
||||
:ci="ci"
|
||||
:attr="attr"
|
||||
:attributeGroups="attributeGroups"
|
||||
@updateChoiceValue="updateChoiceValue"
|
||||
@refresh="refresh"
|
||||
@updateCIByself="updateCIByself"
|
||||
@refreshReferenceAttr="handleReferenceAttr"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CIRelationTable
|
||||
:ciId="ciId"
|
||||
:typeId="typeId"
|
||||
:ci="ci"
|
||||
:relationData="relationData"
|
||||
@refreshRelationCI="refreshRelationCI(ciId)"
|
||||
/>
|
||||
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
|
||||
<div class="ci-detail-attr">
|
||||
<el-descriptions
|
||||
:title="group.name || $t('other')"
|
||||
:key="group.name"
|
||||
v-for="group in attributeGroups"
|
||||
border
|
||||
:column="3"
|
||||
>
|
||||
<el-descriptions-item
|
||||
:label="`${attr.alias || attr.name}`"
|
||||
:key="attr.name"
|
||||
v-for="attr in group.attributes"
|
||||
>
|
||||
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" @refreshReferenceAttr="handleReferenceAttr" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.ci.topo') }}</span>
|
||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
||||
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
|
||||
<CIDetailRelation
|
||||
:ciId="ciId"
|
||||
:typeId="typeId"
|
||||
:ci="ci"
|
||||
:relationData="relationData"
|
||||
/>
|
||||
<ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" :initQueryLoading="initQueryLoading" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
@@ -92,7 +42,7 @@
|
||||
<ops-icon type="veops-export" />{{ $t('export') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
<CIRollbackForm ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
|
||||
<ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
show-overflow
|
||||
@@ -184,33 +134,25 @@
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { Descriptions, DescriptionsItem } from 'element-ui'
|
||||
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
|
||||
import { getCIById, searchCI } from '@/modules/cmdb/api/ci'
|
||||
|
||||
import RelationMixin from './ciDetailMixin/relationMixin.js'
|
||||
|
||||
import CIDetailTitle from './ciDetailComponent/ciDetailTitle.vue'
|
||||
import CIDetailTableTitle from './ciDetailComponent/ciDetailTableTitle.vue'
|
||||
import CIDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CIRelationTable from './ciDetailComponent/ciRelationTable.vue'
|
||||
import CIDetailRelation from './ciDetailRelation.vue'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
|
||||
import CIRollbackForm from './ciRollbackForm.vue'
|
||||
|
||||
import CiRollbackForm from './ciRollbackForm.vue'
|
||||
export default {
|
||||
name: 'CiDetailTab',
|
||||
mixins: [RelationMixin],
|
||||
components: {
|
||||
CIDetailAttrContent,
|
||||
CIDetailRelation,
|
||||
ElDescriptions: Descriptions,
|
||||
ElDescriptionsItem: DescriptionsItem,
|
||||
CiDetailAttrContent,
|
||||
CiDetailRelation,
|
||||
TriggerTable,
|
||||
RelatedItsmTable,
|
||||
CIRollbackForm,
|
||||
CIDetailTitle,
|
||||
CIDetailTableTitle,
|
||||
CIRelationTable
|
||||
CiRollbackForm,
|
||||
},
|
||||
props: {
|
||||
typeId: {
|
||||
@@ -276,11 +218,15 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async create(ciId, activeTabKey = 'tab_1') {
|
||||
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.initQueryLoading = true
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
|
||||
await this.getCI()
|
||||
await this.judgeItsmInstalled()
|
||||
if (this.hasPermission) {
|
||||
@@ -288,15 +234,16 @@ export default {
|
||||
this.getCIHistory()
|
||||
const ciTypeRes = await getCITypes()
|
||||
this.ci_types = ciTypeRes.ci_types
|
||||
|
||||
this.initRelationData(this.typeId, this.ciId)
|
||||
if (this.activeTabKey === 'tab_2') {
|
||||
this.$refs.ciDetailRelation.init(true)
|
||||
}
|
||||
}
|
||||
this.initQueryLoading = false
|
||||
},
|
||||
getAttributes() {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
.then((res) => {
|
||||
this.attributeGroups = (res || []).filter((group) => group?.attributes?.length)
|
||||
this.attributeGroups = res
|
||||
|
||||
this.handleReferenceAttr()
|
||||
})
|
||||
@@ -562,68 +509,23 @@ export default {
|
||||
.ant-tabs-extra-content {
|
||||
line-height: 44px;
|
||||
}
|
||||
.ci-detail-table {
|
||||
.ci-detail-attr {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
|
||||
&-attr {
|
||||
width: 100%;
|
||||
margin-top: 14px;
|
||||
|
||||
&-wrap {
|
||||
padding: 13px;
|
||||
width: 100%;
|
||||
border: solid 1px #E4E7ED;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&-group {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: @text-color_1;
|
||||
margin-bottom: 7.5px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: @text-color_3;
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
|
||||
&-text {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
&-colon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&:hover a {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.el-descriptions-item__content {
|
||||
cursor: default;
|
||||
&:hover a {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-descriptions:first-child > .el-descriptions__header {
|
||||
margin-top: 0;
|
||||
}
|
||||
.el-descriptions__header {
|
||||
margin-bottom: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@@ -1,85 +0,0 @@
|
||||
<template>
|
||||
<a-form-model
|
||||
:model="formData"
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="{ span: 6 }"
|
||||
class="attr-ad-form"
|
||||
>
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.ciType.defaultVersion')"
|
||||
>
|
||||
<a-select
|
||||
v-model="formData.version"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option value="1">
|
||||
v1
|
||||
</a-select-option>
|
||||
<a-select-option value="2c">
|
||||
v2c
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.ciType.defaultCommunity')"
|
||||
>
|
||||
<a-input v-model="formData.community" />
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.ciType.timeout')"
|
||||
:extra="$t('cmdb.ciType.snmpFormTip2')"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="formData.timeout"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.ciType.retryCount')"
|
||||
:extra="$t('cmdb.ciType.snmpFormTip3')"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="formData.retries"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SNMPConfig',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
inject: ['provide_labelCol'],
|
||||
computed: {
|
||||
formData: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(newValue) {
|
||||
this.$emit('change', newValue)
|
||||
}
|
||||
},
|
||||
labelCol() {
|
||||
return this.provide_labelCol()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
@@ -1,68 +0,0 @@
|
||||
<template>
|
||||
<a-form-model
|
||||
:model="formData"
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="{ span: 6 }"
|
||||
class="attr-ad-form"
|
||||
>
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.ciType.initialNode')"
|
||||
:extra="$t('cmdb.ciType.snmpFormTip4')"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.initial_node"
|
||||
:placeholder="$t('cmdb.ciType.defaultGateway')"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.ciType.recursiveOrNot')"
|
||||
:extra="$t('cmdb.ciType.snmpFormTip5')"
|
||||
>
|
||||
<a-switch v-model="formData.recursive_scan" />
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.ciType.maximumDepth')"
|
||||
:extra="$t('cmdb.ciType.snmpFormTip6')"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="formData.max_depth"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SNMPScanningConfig',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
inject: ['provide_labelCol'],
|
||||
computed: {
|
||||
formData: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(newValue) {
|
||||
this.$emit('change', newValue)
|
||||
}
|
||||
},
|
||||
labelCol() {
|
||||
return this.provide_labelCol()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,35 +1,40 @@
|
||||
<template>
|
||||
<a-form-item
|
||||
label="CIDR"
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="{ span: 6 }"
|
||||
:extra="$t('cmdb.ciType.snmpFormTip7')"
|
||||
>
|
||||
<div class="cidr-tag">
|
||||
<div
|
||||
v-for="(item) in list"
|
||||
:key="item.id"
|
||||
class="cidr-tag-item"
|
||||
<a-row class="attr-ad-form">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
label="CIDR"
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="{ span: 18 }"
|
||||
labelAlign="right"
|
||||
style="width: 100%; margin-top: 20px"
|
||||
>
|
||||
<a-tooltip :title="item.value">
|
||||
<span class="cidr-tag-text">{{ item.value }}</span>
|
||||
</a-tooltip>
|
||||
<a-icon
|
||||
class="cidrv-tag-close"
|
||||
type="close"
|
||||
@click.stop="clickClose(item.id)"
|
||||
/>
|
||||
</div>
|
||||
<a-input
|
||||
v-if="showAddInput"
|
||||
class="cidr-tag-input"
|
||||
autofocus
|
||||
@blur="addPreValue"
|
||||
@pressEnter="showAddInput = false"
|
||||
></a-input>
|
||||
<a v-else class="cidr-tag-add" @click="showAddInput = true">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<div class="cidr-tag">
|
||||
<div
|
||||
v-for="(item) in list"
|
||||
:key="item.id"
|
||||
class="cidr-tag-item"
|
||||
>
|
||||
<a-tooltip :title="item.value">
|
||||
<span class="cidr-tag-text">{{ item.value }}</span>
|
||||
</a-tooltip>
|
||||
<a-icon
|
||||
class="cidrv-tag-close"
|
||||
type="close"
|
||||
@click.stop="clickClose(item.id)"
|
||||
/>
|
||||
</div>
|
||||
<a-input
|
||||
v-if="showAddInput"
|
||||
class="cidr-tag-input"
|
||||
autofocus
|
||||
@blur="addPreValue"
|
||||
@pressEnter="showAddInput = false"
|
||||
></a-input>
|
||||
<a v-else class="cidr-tag-add" @click="showAddInput = true">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@@ -1,160 +0,0 @@
|
||||
<template>
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="{ span: 18 }"
|
||||
>
|
||||
<span slot="label">
|
||||
{{ $t('cmdb.ciType.nodeList') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.snmpFormTip1')">
|
||||
<a-icon type="question-circle" />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<div class="node-setting-wrap">
|
||||
<ops-table
|
||||
:data="nodes"
|
||||
size="mini"
|
||||
show-header-overflow
|
||||
:row-config="{ height: 42 }"
|
||||
border
|
||||
:min-height="78"
|
||||
>
|
||||
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingIp')">
|
||||
<template #default="{ row }">
|
||||
<a-input v-model="row.ip"></a-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingCommunity')">
|
||||
<template #default="{ row }">
|
||||
<a-input v-model="row.community"></a-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingVersion')">
|
||||
<template #default="{ row }">
|
||||
<a-select
|
||||
v-model="row.version"
|
||||
:placeholder="$t('cmdb.ciType.nodeSettingVersionTip')"
|
||||
allowClear
|
||||
class="node-setting-select"
|
||||
>
|
||||
<a-select-option value="1">
|
||||
v1
|
||||
</a-select-option>
|
||||
<a-select-option value="2c">
|
||||
v2c
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column min-wdith="90">
|
||||
<template #default="{ row }">
|
||||
<div class="action">
|
||||
<a @click="() => copyNode(row.id)">
|
||||
<a-icon type="copy" />
|
||||
</a>
|
||||
<a @click="() => removeNode(row.id, 1)">
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a @click="addNode">
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</ops-table>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'MonitorNodeSetting',
|
||||
inject: ['provide_labelCol'],
|
||||
props: {
|
||||
form: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodes: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelCol() {
|
||||
return this.provide_labelCol()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initNodesFunc(nodes) {
|
||||
this.nodes = _.cloneDeep(nodes)
|
||||
},
|
||||
addNode() {
|
||||
const newNode = {
|
||||
id: uuidv4(),
|
||||
ip: '',
|
||||
community: 'public',
|
||||
version: '',
|
||||
}
|
||||
this.nodes.push(newNode)
|
||||
},
|
||||
removeNode(removeId, minLength) {
|
||||
if (this.nodes.length <= minLength) {
|
||||
this.$message.error('不可再删除!')
|
||||
return
|
||||
}
|
||||
const _idx = this.nodes.findIndex((item) => item.id === removeId)
|
||||
if (_idx > -1) {
|
||||
this.nodes.splice(_idx, 1)
|
||||
}
|
||||
},
|
||||
copyNode(id) {
|
||||
const copyNode = this.nodes.find((item) => item.id === id)
|
||||
if (copyNode) {
|
||||
const newNode = {
|
||||
...copyNode,
|
||||
id: uuidv4(),
|
||||
}
|
||||
this.nodes.push(newNode)
|
||||
}
|
||||
},
|
||||
|
||||
getNodeValue() {
|
||||
const nodes = this.nodes.map((node) => {
|
||||
return _.pick(node, ['ip', 'community', 'version'])
|
||||
})
|
||||
return nodes
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.node-setting-wrap {
|
||||
max-width: 600px;
|
||||
|
||||
.ant-row {
|
||||
/deep/ .ant-input-clear-icon {
|
||||
color: rgba(0,0,0,.25);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-setting-select {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
@@ -54,20 +54,11 @@
|
||||
/>
|
||||
</div>
|
||||
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.SNMP">
|
||||
<div class="attr-ad-header">{{ $t('cmdb.ciType.scanningParameter') }}</div>
|
||||
<div class="attr-ad-form attr-ad-snmp-form">
|
||||
<div class="attr-ad-snmp-form-title">
|
||||
{{ $t('cmdb.ciType.SNMPConfiguration') }}
|
||||
</div>
|
||||
<NodeSetting ref="nodeSetting" />
|
||||
<SNMPConfig v-model="SNMPScanningConfigForm" />
|
||||
|
||||
<div class="attr-ad-snmp-form-title">
|
||||
{{ $t('cmdb.ciType.scanningConfiguration') }}
|
||||
</div>
|
||||
<SNMPScanningConfig v-model="SNMPScanningConfigForm" />
|
||||
<CIDRTags v-model="SNMPScanningConfigForm.cidr" />
|
||||
</div>
|
||||
<div class="attr-ad-header">{{ $t('cmdb.ciType.nodeConfig') }}</div>
|
||||
<a-form :form="nodeSettingForm" layout="inline" class="attr-ad-snmp-form">
|
||||
<NodeSetting ref="nodeSetting" :initNodes="nodes" />
|
||||
<CIDRTags v-model="cidrList" />
|
||||
</a-form>
|
||||
</template>
|
||||
<div class="attr-ad-header">{{ $t('cmdb.ciType.adExecConfig') }}</div>
|
||||
<a-form-model
|
||||
@@ -186,15 +177,13 @@ import { TAB_KEY } from './attrAD/constants.js'
|
||||
import HttpSnmpAD from '../../components/httpSnmpAD'
|
||||
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
|
||||
import CMDBExprDrawer from '@/components/CMDBExprDrawer'
|
||||
import NodeSetting from './attrAD/nodeSetting/index.vue'
|
||||
import NodeSetting from '@/modules/cmdb/components/nodeSetting/index.vue'
|
||||
import AttrADTest from './attrADTest.vue'
|
||||
import { Popover } from 'element-ui'
|
||||
import VcenterForm from './attrAD/privateCloud/vcenterForm.vue'
|
||||
import PublicCloud from './attrAD/publicCloud/index.vue'
|
||||
import PortScanConfig from './attrAD/portScanConfig/index.vue'
|
||||
import CIDRTags from './attrAD/cidrTags/index.vue'
|
||||
import SNMPScanningConfig from './attrAD/SNMPScanningConfig/index.vue'
|
||||
import SNMPConfig from './attrAD/SNMPConfig/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'AttrADTabpane',
|
||||
@@ -209,9 +198,7 @@ export default {
|
||||
VcenterForm,
|
||||
PublicCloud,
|
||||
PortScanConfig,
|
||||
CIDRTags,
|
||||
SNMPScanningConfig,
|
||||
SNMPConfig
|
||||
CIDRTags
|
||||
},
|
||||
props: {
|
||||
adr_id: {
|
||||
@@ -276,6 +263,14 @@ export default {
|
||||
cronVisible: false,
|
||||
intervalValue: 3,
|
||||
agent_type: 'agent_id',
|
||||
nodes: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
ip: '',
|
||||
community: 'public',
|
||||
version: '',
|
||||
},
|
||||
],
|
||||
nodeSettingForm: this.$form.createForm(this, { name: 'snmp_form' }),
|
||||
uniqueKey: '',
|
||||
isPrivateCloud: false,
|
||||
@@ -283,16 +278,7 @@ export default {
|
||||
PRIVATE_CLOUD_NAME,
|
||||
DISCOVERY_CATEGORY_TYPE,
|
||||
isClient: false, // 是否前端新增临时数据
|
||||
SNMPScanningConfigForm: {
|
||||
version: '2c',
|
||||
community: 'public',
|
||||
timeout: 5,
|
||||
retries: 3,
|
||||
initial_node: '',
|
||||
recursive_scan: true,
|
||||
max_depth: 5,
|
||||
cidr: []
|
||||
}, // snmp scanning config form data
|
||||
cidrList: [],
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
@@ -337,13 +323,13 @@ export default {
|
||||
const isEn = this.$i18n.locale === 'en'
|
||||
return {
|
||||
xl: {
|
||||
span: isEn ? 4 : 3
|
||||
span: isEn ? 4 : 2
|
||||
},
|
||||
lg: {
|
||||
span: isEn ? 5 : 4
|
||||
span: isEn ? 5 : 3
|
||||
},
|
||||
sm: {
|
||||
span: isEn ? 6 : 5
|
||||
span: isEn ? 6 : 4
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,13 +404,7 @@ export default {
|
||||
}
|
||||
|
||||
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
|
||||
const extra_option = _findADT?.extra_option ?? {}
|
||||
const {
|
||||
nodes,
|
||||
cidr = []
|
||||
} = extra_option
|
||||
|
||||
const initializeNodes = nodes?.length ? nodes : [
|
||||
const nodes = _findADT?.extra_option?.nodes?.length ? _findADT?.extra_option?.nodes : [
|
||||
{
|
||||
id: uuidv4(),
|
||||
ip: '',
|
||||
@@ -432,11 +412,13 @@ export default {
|
||||
version: '',
|
||||
},
|
||||
]
|
||||
this.nodes = nodes
|
||||
this.$nextTick(() => {
|
||||
this.$refs.nodeSetting.initNodesFunc(initializeNodes)
|
||||
this.$refs.nodeSetting.initNodesFunc()
|
||||
})
|
||||
|
||||
let cidrList = []
|
||||
const cidr = _findADT?.extra_option?.cidr
|
||||
if (Array.isArray(cidr) && cidr?.length) {
|
||||
cidrList = cidr.map((v) => {
|
||||
return {
|
||||
@@ -445,16 +427,7 @@ export default {
|
||||
}
|
||||
})
|
||||
}
|
||||
this.SNMPScanningConfigForm = {
|
||||
version: extra_option?.version ?? '2c',
|
||||
community: extra_option?.community ?? 'public',
|
||||
timeout: extra_option?.timeout ?? 5,
|
||||
retries: extra_option?.retries ?? 3,
|
||||
initial_node: extra_option?.initial_node ?? '',
|
||||
recursive_scan: extra_option?.recursive_scan ?? true,
|
||||
max_depth: extra_option?.max_depth ?? 5,
|
||||
cidr: cidrList
|
||||
}
|
||||
this.cidrList = cidrList
|
||||
}
|
||||
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
|
||||
this.tableData = (_find?.attributes || []).map((item) => {
|
||||
@@ -528,27 +501,12 @@ export default {
|
||||
}
|
||||
|
||||
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
|
||||
const {
|
||||
cidr,
|
||||
...otherConfigForm
|
||||
} = this.SNMPScanningConfigForm
|
||||
const nodes = this.$refs.nodeSetting?.getNodeValue() ?? []
|
||||
|
||||
params = {
|
||||
extra_option: {
|
||||
...otherConfigForm,
|
||||
nodes,
|
||||
cidr: cidr?.map((item) => item.value) || []
|
||||
nodes: this.$refs.nodeSetting?.getNodeValue() ?? [],
|
||||
cidr: this?.cidrList?.map((item) => item.value) || []
|
||||
},
|
||||
}
|
||||
|
||||
if (
|
||||
!otherConfigForm?.recursive_scan &&
|
||||
nodes?.some((item) => !item?.ip)
|
||||
) {
|
||||
this.$message.error(this.$t('cmdb.ciType.recursiveTip'))
|
||||
return
|
||||
}
|
||||
}
|
||||
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
|
||||
const $table = this.$refs.attrMapTable
|
||||
@@ -803,18 +761,8 @@ export default {
|
||||
}
|
||||
}
|
||||
.attr-ad-snmp-form {
|
||||
&-title {
|
||||
font-size: 16px;
|
||||
color: #000000;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/deep/ .ant-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .ant-form-extra {
|
||||
font-size: 12px;
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -179,11 +179,7 @@
|
||||
:filterOption="filterOption"
|
||||
@change="changeChild"
|
||||
>
|
||||
<a-select-option
|
||||
:value="CIType.id"
|
||||
:key="CIType.id"
|
||||
v-for="CIType in CITypes"
|
||||
>
|
||||
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in CITypes">
|
||||
{{ CIType.alias || CIType.name }}
|
||||
<span class="model-select-name">({{ CIType.name }})</span>
|
||||
</a-select-option>
|
||||
@@ -514,11 +510,7 @@ export default {
|
||||
})
|
||||
},
|
||||
filterOption(input, option) {
|
||||
const inputValue = input.toLowerCase()
|
||||
const alias = option.componentOptions.children[0].text.toLowerCase()
|
||||
const name = option.componentOptions.children[1]?.elm?.innerHTML?.toLowerCase?.() ?? ''
|
||||
|
||||
return alias.indexOf(inputValue) >= 0 || name.indexOf(inputValue) >= 0
|
||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
rowClass({ row }) {
|
||||
if (row.isDivider) return 'relation-table-divider'
|
||||
|
@@ -112,18 +112,11 @@ export default {
|
||||
})
|
||||
let CIList = res?.result || []
|
||||
|
||||
const {
|
||||
show_key = '',
|
||||
unique_id = '',
|
||||
attributes = []
|
||||
} = this?.currentCITYpe || {}
|
||||
const unique_key = attributes?.find((attr) => attr?.id === unique_id)?.name || ''
|
||||
|
||||
if (CIList.length) {
|
||||
CIList = CIList.map((item) => {
|
||||
return {
|
||||
value: item?._id,
|
||||
name: item?.[show_key] || item?.[unique_key] || item?._id || '',
|
||||
name: item?.[this?.currentCITYpe?.show_key] || item?._id || '',
|
||||
unitCount: item?.u_count ?? 0
|
||||
}
|
||||
})
|
||||
|
@@ -1,82 +1,79 @@
|
||||
<template>
|
||||
<div ref="wrapRef">
|
||||
<a-spin :tip="loadTip" :spinning="loading" >
|
||||
<div class="table-header">
|
||||
<SearchForm
|
||||
ref="search"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
:typeId="addressCITypeId"
|
||||
:selectedRowKeys="selectedRowKeys"
|
||||
@copyExpression="copyExpression"
|
||||
@refresh="handleSearch"
|
||||
>
|
||||
<div class="ops-list-batch-action" v-show="!!selectedRowKeys.length">
|
||||
<span @click="$refs.create.handleOpen(true, 'update')">{{ $t('update') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="openBatchDownload">{{ $t('download') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</div>
|
||||
</SearchForm>
|
||||
|
||||
<div class="table-header-right">
|
||||
<EditAttrsPopover
|
||||
:typeId="addressCITypeId"
|
||||
@refresh="refreshAfterEditAttrs"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>
|
||||
<ops-icon type="veops-configuration_table" />
|
||||
{{ $t('cmdb.configTable') }}
|
||||
</a-button>
|
||||
</EditAttrsPopover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CITable
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
:attrList="preferenceAttrList"
|
||||
:columns="columns"
|
||||
:data="instanceList"
|
||||
:height="tableHeight"
|
||||
@sort-change="handleSortCol"
|
||||
@openDetail="openDetail"
|
||||
@deleteCI="deleteCI"
|
||||
@onSelectChange="onSelectChange"
|
||||
<div class="table-header">
|
||||
<SearchForm
|
||||
ref="search"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
:typeId="addressCITypeId"
|
||||
@copyExpression="copyExpression"
|
||||
@refresh="handleSearch"
|
||||
/>
|
||||
|
||||
<div class="table-pagination">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
:current="page"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
@change="handleChangePage"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
<div class="table-header-right">
|
||||
<EditAttrsPopover
|
||||
:typeId="addressCITypeId"
|
||||
@refresh="refreshAfterEditAttrs"
|
||||
>
|
||||
<template slot="buildOptionText" slot-scope="props">
|
||||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||
</template>
|
||||
</a-pagination>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>
|
||||
<ops-icon type="veops-configuration_table" />
|
||||
{{ $t('cmdb.configTable') }}
|
||||
</a-button>
|
||||
</EditAttrsPopover>
|
||||
<a-button
|
||||
v-if="instanceList && instanceList.length"
|
||||
type="primary"
|
||||
class="ops-button-ghost"
|
||||
ghost
|
||||
@click="handleExport"
|
||||
>
|
||||
<ops-icon type="veops-export" />
|
||||
{{ $t('export') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<CITable
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
:attrList="preferenceAttrList"
|
||||
:columns="columns"
|
||||
:data="instanceList"
|
||||
:height="tableHeight"
|
||||
@sort-change="handleSortCol"
|
||||
@openDetail="openDetail"
|
||||
@deleteCI="deleteCI"
|
||||
/>
|
||||
|
||||
<div class="table-pagination">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
:current="page"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
@change="handleChangePage"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
>
|
||||
<template slot="buildOptionText" slot-scope="props">
|
||||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||
</template>
|
||||
</a-pagination>
|
||||
</div>
|
||||
|
||||
<BatchDownload
|
||||
ref="batchDownload"
|
||||
@@ -85,12 +82,6 @@
|
||||
/>
|
||||
|
||||
<CIDetailDrawer ref="detail" :typeId="addressCITypeId" />
|
||||
|
||||
<CreateInstanceForm
|
||||
ref="create"
|
||||
:typeIdFromRelation="addressCITypeId"
|
||||
@submit="batchUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -99,7 +90,7 @@ import _ from 'lodash'
|
||||
import { mapState } from 'vuex'
|
||||
import ExcelJS from 'exceljs'
|
||||
import FileSaver from 'file-saver'
|
||||
import { searchCI, deleteCI, updateCI } from '@/modules/cmdb/api/ci'
|
||||
import { searchCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { getCITableColumns } from '@/modules/cmdb/utils/helper'
|
||||
@@ -109,7 +100,6 @@ import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||||
import BatchDownload from '@/modules/cmdb/components/batchDownload/batchDownload.vue'
|
||||
import CIDetailDrawer from '@/modules/cmdb/views/ci/modules/ciDetailDrawer.vue'
|
||||
import EditAttrsPopover from '@/modules/cmdb/views/ci/modules/editAttrsPopover.vue'
|
||||
import CreateInstanceForm from '@/modules/cmdb/views/ci/modules/CreateInstanceForm'
|
||||
|
||||
export default {
|
||||
name: 'IPSearch',
|
||||
@@ -118,8 +108,7 @@ export default {
|
||||
CITable,
|
||||
BatchDownload,
|
||||
CIDetailDrawer,
|
||||
EditAttrsPopover,
|
||||
CreateInstanceForm
|
||||
EditAttrsPopover
|
||||
},
|
||||
props: {
|
||||
addressCIType: {
|
||||
@@ -133,7 +122,6 @@ export default {
|
||||
pageSize: 50,
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
loading: false,
|
||||
loadTip: '',
|
||||
sortByTable: undefined,
|
||||
|
||||
instanceList: [],
|
||||
@@ -142,7 +130,6 @@ export default {
|
||||
preferenceAttrList: [],
|
||||
attrList: [],
|
||||
attributes: {},
|
||||
selectedRowKeys: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -288,7 +275,7 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
openBatchDownload() {
|
||||
handleExport() {
|
||||
this.$refs.batchDownload.open({
|
||||
preferenceAttrList: this.preferenceAttrList,
|
||||
ciTypeName: this.$t('cmdb.ipam.ipSearch') || '',
|
||||
@@ -349,7 +336,6 @@ export default {
|
||||
FileSaver.saveAs(file, `${filename}.xlsx`)
|
||||
})
|
||||
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
},
|
||||
@@ -375,120 +361,6 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
onSelectChange(records) {
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
},
|
||||
|
||||
batchDelete() {
|
||||
this.$confirm({
|
||||
title: this.$t('warning'),
|
||||
content: this.$t('confirmDelete'),
|
||||
onOk: () => {
|
||||
this.batchDeleteAsync()
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
async batchDeleteAsync() {
|
||||
let successNum = 0
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = this.$t('cmdb.ci.batchDeleting')
|
||||
|
||||
const floor = Math.ceil(this.selectedRowKeys.length / 6)
|
||||
for (let i = 0; i < floor; i++) {
|
||||
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
|
||||
const promises = itemList.map((x) => deleteCI(x, false))
|
||||
await Promise.allSettled(promises)
|
||||
.then((res) => {
|
||||
res.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
successNum += 1
|
||||
} else {
|
||||
errorNum += 1
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = this.$t('cmdb.ci.batchDeleting2', {
|
||||
total: this.selectedRowKeys.length,
|
||||
successNum: successNum,
|
||||
errorNum: errorNum,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
this.loadTip = ''
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.$nextTick(() => {
|
||||
this.page = 1
|
||||
this.getTableData()
|
||||
})
|
||||
},
|
||||
|
||||
batchUpdate(values) {
|
||||
this.$confirm({
|
||||
title: this.$t('warning'),
|
||||
content: this.$t('cmdb.ci.batchUpdateConfirm'),
|
||||
onOk: () => {
|
||||
this.batchUpdateAsync(values)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
async batchUpdateAsync(values) {
|
||||
let successNum = 0
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress') + '...'
|
||||
|
||||
const payload = {}
|
||||
Object.keys(values).forEach((key) => {
|
||||
if (values[key] === undefined || values[key] === null) {
|
||||
payload[key] = null
|
||||
} else {
|
||||
payload[key] = values[key]
|
||||
}
|
||||
})
|
||||
this.$refs.create.visible = false
|
||||
const key = 'updatable'
|
||||
let errorMsg = ''
|
||||
|
||||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||||
await updateCI(this.selectedRowKeys[i], payload, false)
|
||||
.then(() => {
|
||||
successNum += 1
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
|
||||
this.$notification.warning({
|
||||
key,
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces', overflow: 'auto', maxHeight: this.windowHeight - 80 + 'px' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress2', {
|
||||
total: this.selectedRowKeys.length,
|
||||
successNum: successNum,
|
||||
errorNum: errorNum,
|
||||
})
|
||||
})
|
||||
}
|
||||
this.loading = false
|
||||
this.loadTip = ''
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.getTableData()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,82 +1,79 @@
|
||||
<template>
|
||||
<div ref="wrapRef">
|
||||
<a-spin :tip="loadTip" :spinning="loading" >
|
||||
<div class="table-header">
|
||||
<SearchForm
|
||||
ref="search"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
:typeId="subnetCITypeId"
|
||||
:selectedRowKeys="selectedRowKeys"
|
||||
@copyExpression="copyExpression"
|
||||
@refresh="handleSearch"
|
||||
>
|
||||
<div class="ops-list-batch-action" v-show="!!selectedRowKeys.length">
|
||||
<span @click="$refs.create.handleOpen(true, 'update')">{{ $t('update') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="openBatchDownload">{{ $t('download') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</div>
|
||||
</SearchForm>
|
||||
|
||||
<div class="table-header-right">
|
||||
<EditAttrsPopover
|
||||
:typeId="subnetCITypeId"
|
||||
@refresh="refreshAfterEditAttrs"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>
|
||||
<ops-icon type="veops-configuration_table" />
|
||||
{{ $t('cmdb.configTable') }}
|
||||
</a-button>
|
||||
</EditAttrsPopover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CITable
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
:attrList="preferenceAttrList"
|
||||
:columns="columns"
|
||||
:data="instanceList"
|
||||
:height="tableHeight"
|
||||
@sort-change="handleSortCol"
|
||||
@openDetail="openDetail"
|
||||
@deleteCI="deleteCI"
|
||||
@onSelectChange="onSelectChange"
|
||||
<div class="table-header">
|
||||
<SearchForm
|
||||
ref="search"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
:typeId="subnetCITypeId"
|
||||
@copyExpression="copyExpression"
|
||||
@refresh="handleSearch"
|
||||
/>
|
||||
|
||||
<div class="table-pagination">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
:current="page"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
@change="handleChangePage"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
<div class="table-header-right">
|
||||
<EditAttrsPopover
|
||||
:typeId="subnetCITypeId"
|
||||
@refresh="refreshAfterEditAttrs"
|
||||
>
|
||||
<template slot="buildOptionText" slot-scope="props">
|
||||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||
</template>
|
||||
</a-pagination>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>
|
||||
<ops-icon type="veops-configuration_table" />
|
||||
{{ $t('cmdb.configTable') }}
|
||||
</a-button>
|
||||
</EditAttrsPopover>
|
||||
<a-button
|
||||
v-if="instanceList && instanceList.length"
|
||||
type="primary"
|
||||
class="ops-button-ghost"
|
||||
ghost
|
||||
@click="handleExport"
|
||||
>
|
||||
<ops-icon type="veops-export" />
|
||||
{{ $t('export') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<CITable
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
:attrList="preferenceAttrList"
|
||||
:columns="columns"
|
||||
:data="instanceList"
|
||||
:height="tableHeight"
|
||||
@sort-change="handleSortCol"
|
||||
@openDetail="openDetail"
|
||||
@deleteCI="deleteCI"
|
||||
/>
|
||||
|
||||
<div class="table-pagination">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
:current="page"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
@change="handleChangePage"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
>
|
||||
<template slot="buildOptionText" slot-scope="props">
|
||||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||
</template>
|
||||
</a-pagination>
|
||||
</div>
|
||||
|
||||
<BatchDownload
|
||||
ref="batchDownload"
|
||||
@@ -85,12 +82,6 @@
|
||||
/>
|
||||
|
||||
<CIDetailDrawer ref="detail" :typeId="subnetCITypeId" />
|
||||
|
||||
<CreateInstanceForm
|
||||
ref="create"
|
||||
:typeIdFromRelation="subnetCITypeId"
|
||||
@submit="batchUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -99,7 +90,7 @@ import _ from 'lodash'
|
||||
import { mapState } from 'vuex'
|
||||
import ExcelJS from 'exceljs'
|
||||
import FileSaver from 'file-saver'
|
||||
import { searchCI, deleteCI, updateCI } from '@/modules/cmdb/api/ci'
|
||||
import { searchCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { getCITableColumns } from '@/modules/cmdb/utils/helper'
|
||||
@@ -109,7 +100,6 @@ import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||||
import BatchDownload from '@/modules/cmdb/components/batchDownload/batchDownload.vue'
|
||||
import CIDetailDrawer from '@/modules/cmdb/views/ci/modules/ciDetailDrawer.vue'
|
||||
import EditAttrsPopover from '@/modules/cmdb/views/ci/modules/editAttrsPopover.vue'
|
||||
import CreateInstanceForm from '@/modules/cmdb/views/ci/modules/CreateInstanceForm'
|
||||
|
||||
export default {
|
||||
name: 'SubnetList',
|
||||
@@ -118,8 +108,7 @@ export default {
|
||||
CITable,
|
||||
BatchDownload,
|
||||
CIDetailDrawer,
|
||||
EditAttrsPopover,
|
||||
CreateInstanceForm
|
||||
EditAttrsPopover
|
||||
},
|
||||
props: {
|
||||
subnetCIType: {
|
||||
@@ -133,7 +122,6 @@ export default {
|
||||
pageSize: 50,
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
loading: false,
|
||||
loadTip: '',
|
||||
sortByTable: undefined,
|
||||
|
||||
instanceList: [],
|
||||
@@ -142,7 +130,6 @@ export default {
|
||||
preferenceAttrList: [],
|
||||
attrList: [],
|
||||
attributes: {},
|
||||
selectedRowKeys: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -288,7 +275,7 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
openBatchDownload() {
|
||||
handleExport() {
|
||||
this.$refs.batchDownload.open({
|
||||
preferenceAttrList: this.preferenceAttrList,
|
||||
ciTypeName: this.$t('cmdb.ipam.subnetList') || '',
|
||||
@@ -349,7 +336,6 @@ export default {
|
||||
FileSaver.saveAs(file, `${filename}.xlsx`)
|
||||
})
|
||||
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
},
|
||||
@@ -376,120 +362,6 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
onSelectChange(records) {
|
||||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||||
},
|
||||
|
||||
batchDelete() {
|
||||
this.$confirm({
|
||||
title: this.$t('warning'),
|
||||
content: this.$t('confirmDelete'),
|
||||
onOk: () => {
|
||||
this.batchDeleteAsync()
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
async batchDeleteAsync() {
|
||||
let successNum = 0
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = this.$t('cmdb.ci.batchDeleting')
|
||||
|
||||
const floor = Math.ceil(this.selectedRowKeys.length / 6)
|
||||
for (let i = 0; i < floor; i++) {
|
||||
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
|
||||
const promises = itemList.map((x) => deleteCI(x, false))
|
||||
await Promise.allSettled(promises)
|
||||
.then((res) => {
|
||||
res.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
successNum += 1
|
||||
} else {
|
||||
errorNum += 1
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = this.$t('cmdb.ci.batchDeleting2', {
|
||||
total: this.selectedRowKeys.length,
|
||||
successNum: successNum,
|
||||
errorNum: errorNum,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
this.loadTip = ''
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.$nextTick(() => {
|
||||
this.page = 1
|
||||
this.getTableData()
|
||||
})
|
||||
},
|
||||
|
||||
batchUpdate(values) {
|
||||
this.$confirm({
|
||||
title: this.$t('warning'),
|
||||
content: this.$t('cmdb.ci.batchUpdateConfirm'),
|
||||
onOk: () => {
|
||||
this.batchUpdateAsync(values)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
async batchUpdateAsync(values) {
|
||||
let successNum = 0
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress') + '...'
|
||||
|
||||
const payload = {}
|
||||
Object.keys(values).forEach((key) => {
|
||||
if (values[key] === undefined || values[key] === null) {
|
||||
payload[key] = null
|
||||
} else {
|
||||
payload[key] = values[key]
|
||||
}
|
||||
})
|
||||
this.$refs.create.visible = false
|
||||
const key = 'updatable'
|
||||
let errorMsg = ''
|
||||
|
||||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||||
await updateCI(this.selectedRowKeys[i], payload, false)
|
||||
.then(() => {
|
||||
successNum += 1
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
|
||||
this.$notification.warning({
|
||||
key,
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces', overflow: 'auto', maxHeight: this.windowHeight - 80 + 'px' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress2', {
|
||||
total: this.selectedRowKeys.length,
|
||||
successNum: successNum,
|
||||
errorNum: errorNum,
|
||||
})
|
||||
})
|
||||
}
|
||||
this.loading = false
|
||||
this.loadTip = ''
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.getTableData()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -30,13 +30,13 @@
|
||||
>新增</a-button
|
||||
>
|
||||
</SearchForm>
|
||||
<ops-table
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
row-id="_id"
|
||||
:data="tableData"
|
||||
:height="tableHeight"
|
||||
highlight-hover-row
|
||||
:checkbox-config="{ reserve: true, highlight: true, range: true }"
|
||||
:checkbox-config="{ reserve: true }"
|
||||
@checkbox-change="onSelectChange"
|
||||
@checkbox-all="onSelectChange"
|
||||
show-overflow="tooltip"
|
||||
@@ -76,7 +76,7 @@
|
||||
<span v-if="col.value_type == '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</ops-table>
|
||||
</vxe-table>
|
||||
<a-pagination
|
||||
v-model="currentPage"
|
||||
size="small"
|
||||
@@ -216,7 +216,7 @@ export default {
|
||||
this.totalNumber = res.numfound
|
||||
this.columns = this.getColumns(res.result, this.preferenceAttrList)
|
||||
this.$nextTick(() => {
|
||||
const _table = this.$refs.xTable?.getVxetableRef?.()
|
||||
const _table = this.$refs.xTable
|
||||
if (_table) {
|
||||
_table.refreshColumn()
|
||||
}
|
||||
@@ -316,11 +316,7 @@ export default {
|
||||
|
||||
onSelectChange() {},
|
||||
handleClose() {
|
||||
const _table = this.$refs.xTable?.getVxetableRef?.()
|
||||
if (_table) {
|
||||
_table.clearCheckboxRow()
|
||||
}
|
||||
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.currentPage = 1
|
||||
this.expression = ''
|
||||
this.isFocusExpression = false
|
||||
@@ -328,10 +324,8 @@ export default {
|
||||
this.showCreateBtn = true
|
||||
},
|
||||
async handleOk() {
|
||||
const _table = this.$refs.xTable?.getVxetableRef?.()
|
||||
const selectRecordsCurrent = _table?.getCheckboxRecords?.() || []
|
||||
const selectRecordsReserved = _table?.getCheckboxReserveRecords?.() || []
|
||||
|
||||
const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords()
|
||||
const selectRecordsReserved = this.$refs.xTable.getCheckboxReserveRecords()
|
||||
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
|
||||
if (ciIds.length) {
|
||||
if (this.type === 'children') {
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import _ from 'lodash'
|
||||
import i18n from '@/lang'
|
||||
|
||||
export function timeFix() {
|
||||
const time = new Date()
|
||||
const hour = time.getHours()
|
||||
return hour < 9 ? i18n.t('cs.login.welcomeTime1') : hour <= 11 ? i18n.t('cs.login.welcomeTime2') : hour <= 13 ? i18n.t('cs.login.welcomeTime3') : hour < 20 ? i18n.t('cs.login.welcomeTime4') : i18n.t('cs.login.welcomeTime5')
|
||||
return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好'
|
||||
}
|
||||
|
||||
export function welcome() {
|
||||
|
@@ -23,7 +23,7 @@
|
||||
<a-form-model-item :label="$t('cs.auth.oauth2.tokenUrl')" prop="token_url">
|
||||
<a-input v-model="form.token_url" :placeholder="$t('cs.auth.oauth2.tokenUrlPlaceholder')" />
|
||||
</a-form-model-item>
|
||||
<SpanTitle>{{ $t('cs.auth.other') }}</SpanTitle>
|
||||
<SpanTitle>其他</SpanTitle>
|
||||
<a-form-model-item :label="$t('cs.auth.oauth2.userInfo')" prop="user_info" :wrapper-col="{ span: 15 }">
|
||||
<vue-json-editor
|
||||
:style="{ '--custom-height': `${200}px` }"
|
||||
|
@@ -445,27 +445,6 @@ const cs_en = {
|
||||
test: 'Test',
|
||||
selectApp: 'Select App',
|
||||
},
|
||||
login: {
|
||||
loginText: 'OneOps making operations simple',
|
||||
username: 'Username/Email',
|
||||
usernameRequired: 'Please input Username/Email',
|
||||
password: 'Password',
|
||||
passwordRequired: 'Please input Password',
|
||||
captcha: 'Captcha',
|
||||
captchaRequired: 'Please input Captcha',
|
||||
loginBtn: 'Login',
|
||||
autoLogin: 'Auto Login',
|
||||
otherLoginWay: 'Other Login',
|
||||
welcomeMessage: 'Welcome',
|
||||
welcomeDesc: '{name} Welcome Back',
|
||||
welcomeTime1: 'Good Morning',
|
||||
welcomeTime2: 'Good Morning',
|
||||
welcomeTime3: 'Good Afternoon',
|
||||
welcomeTime4: 'Good Afternoon',
|
||||
welcomeTime5: 'Good Evening',
|
||||
oneDeviceLogin: 'Login on one device only',
|
||||
logoutSoon: 'Logging Out Soon...',
|
||||
}
|
||||
}
|
||||
|
||||
export default cs_en
|
||||
|
@@ -443,26 +443,5 @@ const cs_zh = {
|
||||
test: '测试',
|
||||
selectApp: '选择应用',
|
||||
},
|
||||
login: {
|
||||
loginText: '维易科技 让运维变简单',
|
||||
username: '用户名/邮箱',
|
||||
usernameRequired: '请输入用户名或邮箱',
|
||||
password: '密码',
|
||||
passwordRequired: '请输入密码',
|
||||
captcha: '图片验证码',
|
||||
captchaRequired: '请输入验证码',
|
||||
loginBtn: '登录',
|
||||
autoLogin: '自动登录',
|
||||
otherLoginWay: '其他登录方式',
|
||||
welcomeMessage: '欢迎',
|
||||
welcomeDesc: '{name} 欢迎回来',
|
||||
welcomeTime1: '早上好',
|
||||
welcomeTime2: '上午好',
|
||||
welcomeTime3: '中午好',
|
||||
welcomeTime4: '下午好',
|
||||
welcomeTime5: '晚上好',
|
||||
oneDeviceLogin: '只能在一个设备上登录',
|
||||
logoutSoon: '即将登出...',
|
||||
}
|
||||
}
|
||||
export default cs_zh
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="ops-login">
|
||||
<div class="ops-login-left">
|
||||
<span>{{ $t('cs.login.loginText') }}</span>
|
||||
<span>维易科技 让运维变简单</span>
|
||||
</div>
|
||||
<div class="ops-login-right">
|
||||
<img src="../../assets/logo_VECMDB.png" />
|
||||
@@ -12,7 +12,7 @@
|
||||
@submit="handleSubmit"
|
||||
hideRequiredMark
|
||||
:colon="false">
|
||||
<a-form-item :label="$t('cs.login.username')">
|
||||
<a-form-item label="用户名/邮箱">
|
||||
<a-input
|
||||
size="large"
|
||||
type="text"
|
||||
@@ -20,10 +20,7 @@
|
||||
v-decorator="[
|
||||
'username',
|
||||
{
|
||||
rules: [
|
||||
{ required: true, message: $t('cs.login.usernameRequired') },
|
||||
{ validator: handleUsernameOrEmail }
|
||||
],
|
||||
rules: [{ required: true, message: '请输入用户名或邮箱' }, { validator: handleUsernameOrEmail }],
|
||||
validateTrigger: 'change',
|
||||
},
|
||||
]"
|
||||
@@ -31,24 +28,19 @@
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('cs.login.password')">
|
||||
<a-form-item label="密码">
|
||||
<a-input
|
||||
size="large"
|
||||
type="password"
|
||||
autocomplete="false"
|
||||
class="ops-input"
|
||||
v-decorator="[
|
||||
'password',
|
||||
{ rules: [{ required: true, message: $t('cs.login.passwordRequired') }], validateTrigger: 'blur' }
|
||||
]"
|
||||
v-decorator="['password', { rules: [{ required: true, message: '请输入密码' }], validateTrigger: 'blur' }]"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-checkbox v-decorator="['rememberMe', { valuePropName: 'checked' }]">
|
||||
{{ $t('cs.login.autoLogin') }}
|
||||
</a-checkbox>
|
||||
<a-checkbox v-decorator="['rememberMe', { valuePropName: 'checked' }]">自动登录</a-checkbox>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item style="margin-top:24px">
|
||||
@@ -59,21 +51,17 @@
|
||||
class="login-button"
|
||||
:loading="state.loginBtn"
|
||||
:disabled="state.loginBtn"
|
||||
>登录</a-button
|
||||
>
|
||||
{{ $t('cs.login.loginBtn') }}
|
||||
</a-button>
|
||||
<a-checkbox
|
||||
v-if="hasLDAP"
|
||||
v-if="enable_list && enable_list.length === 1 && enable_list[0].auth_type === 'LDAP'"
|
||||
v-model="auth_with_ldap"
|
||||
>LDAP</a-checkbox
|
||||
>
|
||||
LDAP
|
||||
</a-checkbox>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template v-if="_enable_list && _enable_list.length >= 1">
|
||||
<a-divider style="font-size:14px">
|
||||
{{ $t('cs.login.otherLoginWay') }}
|
||||
</a-divider>
|
||||
<a-divider style="font-size:14px">其他登录方式</a-divider>
|
||||
<div style="text-align:center">
|
||||
<span v-for="(item, index) in _enable_list" :key="item.auth_type">
|
||||
<ops-icon :type="item.auth_type" />
|
||||
@@ -116,20 +104,21 @@ export default {
|
||||
computed: {
|
||||
...mapState({ auth_enable: (state) => state?.user?.auth_enable ?? {} }),
|
||||
enable_list() {
|
||||
return this.auth_enable?.enable_list ?? []
|
||||
},
|
||||
hasLDAP() {
|
||||
return this.enable_list.some((en) => en.auth_type === 'LDAP')
|
||||
return this.auth_enable.enable_list ?? []
|
||||
},
|
||||
_enable_list() {
|
||||
return this.enable_list.filter((en) => en.auth_type !== 'LDAP')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
hasLDAP: {
|
||||
enable_list: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.auth_with_ldap = newVal
|
||||
if (newVal && newVal.length === 1 && newVal[0].auth_type === 'LDAP') {
|
||||
this.auth_with_ldap = true
|
||||
} else {
|
||||
this.auth_with_ldap = false
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -153,7 +142,7 @@ export default {
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
const {
|
||||
hasLDAP,
|
||||
enable_list,
|
||||
form: { validateFields },
|
||||
state,
|
||||
customActiveKey,
|
||||
@@ -171,7 +160,10 @@ export default {
|
||||
delete loginParams.username
|
||||
loginParams.username = values.username
|
||||
loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password
|
||||
loginParams.auth_with_ldap = hasLDAP ? Number(auth_with_ldap) : undefined
|
||||
loginParams.auth_with_ldap =
|
||||
enable_list && enable_list.length === 1 && enable_list[0].auth_type === 'LDAP'
|
||||
? Number(auth_with_ldap)
|
||||
: undefined
|
||||
|
||||
localStorage.setItem('ops_auth_type', '')
|
||||
Login({ userInfo: loginParams })
|
||||
@@ -194,8 +186,8 @@ export default {
|
||||
// 延迟 1 秒显示欢迎信息
|
||||
setTimeout(() => {
|
||||
this.$notification.success({
|
||||
message: this.$t('cs.login.welcomeMessage'),
|
||||
description: this.$t('cs.login.welcomeDesc', { name: timeFix() }),
|
||||
message: '欢迎',
|
||||
description: `${timeFix()},欢迎回来`,
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
cmdb-db:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-db:2.5
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-db:2.3
|
||||
container_name: cmdb-db
|
||||
env_file:
|
||||
- .env
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
- '23306:3306'
|
||||
|
||||
cmdb-cache:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:2.5
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:2.3
|
||||
container_name: cmdb-cache
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
@@ -41,14 +41,13 @@ services:
|
||||
- redis
|
||||
|
||||
cmdb-api:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.5.3
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.5.1
|
||||
container_name: cmdb-api
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
WAIT_HOSTS: cmdb-db:3306, cmdb-cache:6379
|
||||
SYSTEM_DEFAULT_LANGUAGE: # en-US, zh-CN
|
||||
depends_on:
|
||||
cmdb-db:
|
||||
condition: service_healthy
|
||||
@@ -85,7 +84,7 @@ services:
|
||||
test: "ps aux|grep -v grep|grep -v '1 root'|grep gunicorn || exit 1"
|
||||
|
||||
cmdb-ui:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.5.3
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.5.1
|
||||
container_name: cmdb-ui
|
||||
depends_on:
|
||||
cmdb-api:
|
||||
|
@@ -80,12 +80,6 @@ For more detailed features, please visit the [official website](https://veops.cn
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Getting started & staying tuned with us
|
||||
|
||||
Star us, and you will receive all releases notifications from GitHub without any delay!
|
||||
|
||||

|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Set up
|
||||
|
@@ -48,7 +48,7 @@ max_connections=1000
|
||||
slow_query_log = ON
|
||||
slow_query_log_file = /tmp/mysql_slow.log
|
||||
long_query_time = 1
|
||||
sql_mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
|
||||
sql_mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
|
||||
#log-error = /var/log/mysql/error.log
|
||||
# By default we only accept connections from localhost
|
||||
#bind-address = 127.0.0.1
|
||||
|
@@ -26,8 +26,6 @@ server {
|
||||
application/rss+xml
|
||||
image/svg+xml;
|
||||
|
||||
client_max_body_size 100m;
|
||||
|
||||
root /etc/nginx/html;
|
||||
location / {
|
||||
root /etc/nginx/html;
|
||||
|
Reference in New Issue
Block a user