mirror of
https://github.com/veops/cmdb.git
synced 2025-09-03 19:26:56 +08:00
Compare commits
92 Commits
dependabot
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
|
e5fe2bffaa | ||
|
9ec105ca37 | ||
|
1e1c92a3ef | ||
|
0b3cad8215 | ||
|
ed41a72900 | ||
|
27854d4e0a | ||
|
0372459f9d | ||
|
f56378e2b5 | ||
|
00b276d5e7 | ||
|
3faefbe8d3 | ||
|
4261f6fb57 | ||
|
c98199e98e | ||
|
82ea1ddc79 | ||
|
995e581315 | ||
|
ec8f626b8f | ||
|
ec884c92e1 | ||
|
9ee2776bdd | ||
|
7186bdac9c | ||
|
0a9964375a | ||
|
bff45d00b6 | ||
|
e429ad59ff | ||
|
ace160ae19 | ||
|
7036fe023c | ||
|
341f5dba53 | ||
|
5883c1616e | ||
|
bee0a3a3e5 | ||
|
7a79a8bbf7 | ||
|
100a889cb8 | ||
|
b093569453 | ||
|
3919dfdfbb | ||
|
ef85ba2542 | ||
|
3d5c2ec5bc | ||
|
c143d6ae5b | ||
|
10b273ee81 | ||
|
855cb91b31 | ||
|
ffae57642c | ||
|
9ed1108c20 | ||
|
72b2f8b6de | ||
|
20f3e917fe | ||
|
c430515377 | ||
|
a6e2aca281 | ||
|
18313b7bd1 | ||
|
dbc44a8ad6 | ||
|
f143e30cf5 | ||
|
4beece5a6e | ||
|
920295d955 | ||
|
6eb8ae1dac | ||
|
f1f86ce25a | ||
|
090007487d | ||
|
aa98a304c1 | ||
|
fe22e363b4 | ||
|
2d2fb6e1d6 | ||
|
9894c77300 | ||
|
4ea947f741 | ||
|
e5ab2c2573 | ||
|
5581fa8d0f | ||
|
76c939fe5c | ||
|
6e9ce08e2c | ||
|
092a8b9b92 | ||
|
b45fd0cbbb | ||
|
5320ecfd62 | ||
|
1d253d7ad3 | ||
|
73d53f0440 | ||
|
d4a37af183 | ||
|
e03849b054 | ||
|
faed3fe6a2 | ||
|
21c9d9accd | ||
|
a06599ce33 | ||
|
2b69217136 | ||
|
03a3b8b169 | ||
|
cd319421d5 | ||
|
c918d54ea5 | ||
|
cf0ad7bad6 | ||
|
e0c8263542 | ||
|
275e8b15f3 | ||
|
6ff942c107 | ||
|
d3c87ee500 | ||
|
a4f65e7fc6 | ||
|
10527bf9b8 | ||
|
0414121c27 | ||
|
edde467c87 | ||
|
d525e1ec54 | ||
|
91c49b690f | ||
|
05453becf9 | ||
|
5fe27f8678 | ||
|
0924b8846f | ||
|
62a669159a | ||
|
2933bf1efa | ||
|
981f8b0145 | ||
|
213bda671c | ||
|
837aabfe77 | ||
|
cc599d414a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,7 +43,8 @@ cmdb-api/api/uploaded_files
|
||||
cmdb-api/migrations/versions
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
#*.mo
|
||||
messages.pot
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
26
README.md
26
README.md
@@ -73,20 +73,34 @@
|
||||
## 安装
|
||||
|
||||
### Docker 一键快速构建
|
||||
- 进入主目录(先安装 docker 环境, 注意要clone整个项目)
|
||||
|
||||
> 方法一
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第二步: 拷贝项目
|
||||
```shell
|
||||
git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
- 第三步:进入主目录,执行:
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
> 方法二, 该方法适用于linux系统
|
||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||
sh install.sh install
|
||||
```
|
||||
|
||||
### [本地开发环境搭建](docs/local.md)
|
||||
|
||||
### [Makefile 安装](docs/makefile.md)
|
||||
|
||||
## 验证
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
|
||||
|
||||
---
|
||||
|
||||
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
|
||||
|
@@ -25,8 +25,10 @@ Flask-Login = ">=0.6.2"
|
||||
Flask-Bcrypt = "==1.0.1"
|
||||
Flask-Cors = ">=3.0.8"
|
||||
ldap3 = "==2.9.1"
|
||||
pycryptodome = "==3.12.0"
|
||||
pycryptodome = "==3.19.1"
|
||||
cryptography = ">=41.0.2"
|
||||
# i18n
|
||||
flask-babel = "==4.0.0"
|
||||
# Caching
|
||||
Flask-Caching = ">=1.0.0"
|
||||
# Environment variable parsing
|
||||
@@ -62,6 +64,7 @@ alembic = "==1.7.7"
|
||||
hvac = "==2.0.0"
|
||||
colorama = ">=0.4.6"
|
||||
pycryptodomex = ">=3.19.0"
|
||||
lz4 = ">=4.3.2"
|
||||
|
||||
[dev-packages]
|
||||
# Testing
|
||||
|
@@ -12,14 +12,17 @@ from pathlib import Path
|
||||
from flask import Flask
|
||||
from flask import jsonify
|
||||
from flask import make_response
|
||||
from flask import request
|
||||
from flask.blueprints import Blueprint
|
||||
from flask.cli import click
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
from flask_babel.speaklater import LazyString
|
||||
|
||||
import api.views.entry
|
||||
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
|
||||
from api.extensions import (bcrypt, babel, cache, celery, cors, db, es, login_manager, migrate, rd)
|
||||
from api.extensions import inner_secrets
|
||||
from api.flask_cas import CAS
|
||||
from api.lib.perm.authentication.cas import CAS
|
||||
from api.lib.perm.authentication.oauth2 import OAuth2
|
||||
from api.lib.secrets.secrets import InnerKVManger
|
||||
from api.models.acl import User
|
||||
|
||||
@@ -71,7 +74,7 @@ class ReverseProxy(object):
|
||||
|
||||
class MyJSONEncoder(DefaultJSONProvider):
|
||||
def default(self, o):
|
||||
if isinstance(o, (decimal.Decimal, datetime.date, datetime.time)):
|
||||
if isinstance(o, (decimal.Decimal, datetime.date, datetime.time, LazyString)):
|
||||
return str(o)
|
||||
|
||||
if isinstance(o, datetime.datetime):
|
||||
@@ -96,6 +99,7 @@ def create_app(config_object="settings"):
|
||||
register_shell_context(app)
|
||||
register_commands(app)
|
||||
CAS(app)
|
||||
OAuth2(app)
|
||||
app.wsgi_app = ReverseProxy(app.wsgi_app)
|
||||
configure_upload_dir(app)
|
||||
|
||||
@@ -115,7 +119,13 @@ def configure_upload_dir(app):
|
||||
|
||||
def register_extensions(app):
|
||||
"""Register Flask extensions."""
|
||||
|
||||
def get_locale():
|
||||
accept_languages = app.config.get('ACCEPT_LANGUAGES', ['en', 'zh'])
|
||||
return request.accept_languages.best_match(accept_languages)
|
||||
|
||||
bcrypt.init_app(app)
|
||||
babel.init_app(app, locale_selector=get_locale)
|
||||
cache.init_app(app)
|
||||
db.init_app(app)
|
||||
cors.init_app(app)
|
||||
@@ -192,10 +202,11 @@ def configure_logger(app):
|
||||
app.logger.addHandler(handler)
|
||||
|
||||
log_file = app.config['LOG_PATH']
|
||||
file_handler = RotatingFileHandler(log_file,
|
||||
maxBytes=2 ** 30,
|
||||
backupCount=7)
|
||||
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
file_handler.setFormatter(formatter)
|
||||
app.logger.addHandler(file_handler)
|
||||
if log_file and log_file != "/dev/stdout":
|
||||
file_handler = RotatingFileHandler(log_file,
|
||||
maxBytes=2 ** 30,
|
||||
backupCount=7)
|
||||
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
file_handler.setFormatter(formatter)
|
||||
app.logger.addHandler(file_handler)
|
||||
app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
|
@@ -35,8 +35,22 @@ def add_user():
|
||||
|
||||
"""
|
||||
|
||||
from api.models.acl import App
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
|
||||
username = click.prompt('Enter username', confirmation_prompt=False)
|
||||
password = click.prompt('Enter password', hide_input=True, confirmation_prompt=True)
|
||||
email = click.prompt('Enter email ', confirmation_prompt=False)
|
||||
is_admin = click.prompt('Admin (Y/N) ', confirmation_prompt=False, type=bool, default=False)
|
||||
|
||||
UserCRUD.add(username=username, password=password, email=email)
|
||||
|
||||
if is_admin:
|
||||
app = AppCache.get('acl') or App.create(name='acl')
|
||||
acl_admin = RoleCache.get('acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
|
||||
rid = RoleCache.get_by_name(None, username).id
|
||||
|
||||
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)
|
||||
|
@@ -5,6 +5,7 @@ import copy
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import click
|
||||
import requests
|
||||
@@ -19,6 +20,7 @@ from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
@@ -49,12 +51,17 @@ def cmdb_init_cache():
|
||||
|
||||
ci_relations = CIRelation.get_by(to_dict=False)
|
||||
relations = dict()
|
||||
relations2 = dict()
|
||||
for cr in ci_relations:
|
||||
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
if cr.ancestor_ids:
|
||||
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
for i in relations:
|
||||
relations[i] = json.dumps(relations[i])
|
||||
if relations:
|
||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||
if relations2:
|
||||
rd.create_or_update(relations2, REDIS_PREFIX_CI_RELATION2)
|
||||
|
||||
es = None
|
||||
if current_app.config.get("USE_ES"):
|
||||
@@ -111,7 +118,15 @@ def cmdb_init_acl():
|
||||
# 1. add resource type
|
||||
for resource_type in ResourceTypeEnum.all():
|
||||
try:
|
||||
ResourceTypeCRUD.add(app_id, resource_type, '', PermEnum.all())
|
||||
perms = PermEnum.all()
|
||||
if resource_type in (ResourceTypeEnum.CI_FILTER, ResourceTypeEnum.PAGE):
|
||||
perms = [PermEnum.READ]
|
||||
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
||||
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
||||
elif resource_type == ResourceTypeEnum.RELATION_VIEW:
|
||||
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
||||
|
||||
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
||||
except AbortException:
|
||||
pass
|
||||
|
||||
@@ -162,6 +177,11 @@ def cmdb_counter():
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
|
||||
current_app.test_request_context().push()
|
||||
if not UserCache.get('worker'):
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com')
|
||||
|
||||
login_user(UserCache.get('worker'))
|
||||
while True:
|
||||
try:
|
||||
|
@@ -299,3 +299,20 @@ def common_check_new_columns():
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
|
||||
current_app.logger.error(e)
|
||||
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def common_sync_file_to_db():
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
CommonFileCRUD.sync_file_to_db()
|
||||
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
@click.option('--value', type=click.INT, default=-1)
|
||||
def set_auth_auto_redirect_enable(value):
|
||||
if value < 0:
|
||||
return
|
||||
from api.lib.common_setting.common_data import CommonDataCRUD
|
||||
CommonDataCRUD.set_auth_auto_redirect_enable(value)
|
||||
|
@@ -5,9 +5,7 @@ from glob import glob
|
||||
from subprocess import call
|
||||
|
||||
import click
|
||||
from flask import current_app
|
||||
from flask.cli import with_appcontext
|
||||
from werkzeug.exceptions import MethodNotAllowed, NotFound
|
||||
|
||||
from api.extensions import db
|
||||
|
||||
@@ -90,3 +88,40 @@ def db_setup():
|
||||
"""create tables
|
||||
"""
|
||||
db.create_all()
|
||||
|
||||
|
||||
@click.group()
|
||||
def translate():
|
||||
"""Translation and localization commands."""
|
||||
|
||||
|
||||
@translate.command()
|
||||
@click.argument('lang')
|
||||
def init(lang):
|
||||
"""Initialize a new language."""
|
||||
|
||||
if os.system('pybabel extract -F babel.cfg -k _l -o messages.pot .'):
|
||||
raise RuntimeError('extract command failed')
|
||||
if os.system(
|
||||
'pybabel init -i messages.pot -d api/translations -l ' + lang):
|
||||
raise RuntimeError('init command failed')
|
||||
os.remove('messages.pot')
|
||||
|
||||
|
||||
@translate.command()
|
||||
def update():
|
||||
"""Update all languages."""
|
||||
|
||||
if os.system('pybabel extract -F babel.cfg -k _l -o messages.pot .'):
|
||||
raise RuntimeError('extract command failed')
|
||||
if os.system('pybabel update -i messages.pot -d api/translations'):
|
||||
raise RuntimeError('update command failed')
|
||||
os.remove('messages.pot')
|
||||
|
||||
|
||||
@translate.command()
|
||||
def compile():
|
||||
"""Compile all languages."""
|
||||
|
||||
if os.system('pybabel compile -d api/translations'):
|
||||
raise RuntimeError('compile command failed')
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
from celery import Celery
|
||||
from flask_babel import Babel
|
||||
from flask_bcrypt import Bcrypt
|
||||
from flask_caching import Cache
|
||||
from flask_cors import CORS
|
||||
@@ -14,6 +15,7 @@ from api.lib.utils import ESHandler
|
||||
from api.lib.utils import RedisHandler
|
||||
|
||||
bcrypt = Bcrypt()
|
||||
babel = Babel()
|
||||
login_manager = LoginManager()
|
||||
db = SQLAlchemy(session_options={"autoflush": False})
|
||||
migrate = Migrate()
|
||||
|
@@ -189,7 +189,8 @@ class AttributeManager(object):
|
||||
return attr
|
||||
|
||||
def get_attribute(self, key, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
attr = AttributeCache.get(key).to_dict()
|
||||
attr = AttributeCache.get(key) or dict()
|
||||
attr = attr and attr.to_dict()
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(
|
||||
attr["id"],
|
||||
|
@@ -3,11 +3,6 @@ import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
|
||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||
@@ -28,6 +23,10 @@ from api.lib.utils import AESCrypto
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
from api.models.cmdb import AutoDiscoveryRule
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
PWD = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
@@ -251,20 +250,17 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
self.cls.get_by(type_id=kwargs['type_id'], adr_id=kwargs.get('adr_id') or None) and abort(
|
||||
400, ErrFormat.ad_duplicate)
|
||||
|
||||
# self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
|
||||
@staticmethod
|
||||
def _can_add(**kwargs):
|
||||
|
||||
if kwargs.get('adr_id'):
|
||||
adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
|
||||
if not adr.is_plugin:
|
||||
other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
|
||||
if other:
|
||||
ci_type = CITypeCache.get(other.type_id)
|
||||
return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
|
||||
# if not adr.is_plugin:
|
||||
# other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
|
||||
# if other:
|
||||
# ci_type = CITypeCache.get(other.type_id)
|
||||
# return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
|
||||
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
kwargs = check_plugin_script(**kwargs)
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,427 +1,344 @@
|
||||
[
|
||||
{
|
||||
"name": "amiLaunchIndex",
|
||||
"type": "整数",
|
||||
"desc": "The AMI launch index, which can be used to find this instance in the launch group.",
|
||||
"example": "0"
|
||||
},
|
||||
{
|
||||
"name": "architecture",
|
||||
"type": "文本",
|
||||
"desc": "The architecture of the image.",
|
||||
"example": "x86_64"
|
||||
},
|
||||
{
|
||||
"name": "blockDeviceMapping",
|
||||
"type": "json",
|
||||
"desc": "Any block device mapping entries for the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"deviceName": "/dev/xvda",
|
||||
"ebs": {
|
||||
"volumeId": "vol-1234567890abcdef0",
|
||||
"status": "attached",
|
||||
"attachTime": "2015-12-22T10:44:09.000Z",
|
||||
"deleteOnTermination": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bootMode",
|
||||
"type": "文本",
|
||||
"desc": "The boot mode that was specified by the AMI. If the value is uefi-preferred, the AMI supports both UEFI and Legacy BIOS. The currentInstanceBootMode parameter is the boot mode that is used to boot the instance at launch or start.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "capacityReservationId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the Capacity Reservation.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "capacityReservationSpecification",
|
||||
"type": "json",
|
||||
"desc": "Information about the Capacity Reservation targeting option.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "clientToken",
|
||||
"type": "文本",
|
||||
"desc": "The idempotency token you provided when you launched the instance, if applicable.",
|
||||
"example": "xMcwG14507example"
|
||||
},
|
||||
{
|
||||
"name": "cpuOptions",
|
||||
"type": "json",
|
||||
"desc": "The CPU options for the instance.",
|
||||
"example": {
|
||||
"coreCount": "1",
|
||||
"threadsPerCore": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "currentInstanceBootMode",
|
||||
"type": "文本",
|
||||
"desc": "The boot mode that is used to boot the instance at launch or start. For more information, see Boot modes in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "dnsName",
|
||||
"type": "文本",
|
||||
"desc": "[IPv4 only] The public DNS name assigned to the instance. This name is not available until the instance enters the running state. This name is only available if you've enabled DNS hostnames for your VPC.",
|
||||
"example": "ec2-54-194-252-215.eu-west-1.compute.amazonaws.com"
|
||||
},
|
||||
{
|
||||
"name": "ebsOptimized",
|
||||
"type": "Boolean",
|
||||
"desc": "Indicates whether the instance is optimized for Amazon EBS I/O. This optimization provides dedicated throughput to Amazon EBS and an optimized configuration stack to provide optimal I/O performance. This optimization isn't available with all instance types. Additional usage charges apply when using an EBS Optimized instance.",
|
||||
"example": "false"
|
||||
},
|
||||
{
|
||||
"name": "elasticGpuAssociationSet",
|
||||
"type": "json",
|
||||
"desc": "The Elastic GPU associated with the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "elasticInferenceAcceleratorAssociationSet",
|
||||
"type": "json",
|
||||
"desc": "The elastic inference accelerator associated with the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "enaSupport",
|
||||
"type": "Boolean",
|
||||
"desc": "Specifies whether enhanced networking with ENA is enabled.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "enclaveOptions",
|
||||
"type": "json",
|
||||
"desc": "Indicates whether the instance is enabled for AWS Nitro Enclaves.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "groupSet",
|
||||
"type": "json",
|
||||
"desc": "The security groups for the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"groupId": "sg-e4076980",
|
||||
"groupName": "SecurityGroup1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hibernationOptions",
|
||||
"type": "json",
|
||||
"desc": "Indicates whether the instance is enabled for hibernation.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "hypervisor",
|
||||
"type": "文本",
|
||||
"desc": "The hypervisor type of the instance. The value xen is used for both Xen and Nitro hypervisors.",
|
||||
"example": "xen"
|
||||
},
|
||||
{
|
||||
"name": "iamInstanceProfile",
|
||||
"type": "json",
|
||||
"desc": "The IAM instance profile associated with the instance, if applicable.",
|
||||
"example": {
|
||||
"arn": "arn:aws:iam::123456789012:instance-profile/AdminRole",
|
||||
"id": "ABCAJEDNCAA64SSD123AB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "imageId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the AMI used to launch the instance.",
|
||||
"example": "ami-bff32ccc"
|
||||
},
|
||||
{
|
||||
"name": "instanceId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the instance.",
|
||||
"example": "i-1234567890abcdef0"
|
||||
},
|
||||
{
|
||||
"name": "instanceLifecycle",
|
||||
"type": "文本",
|
||||
"desc": "Indicates whether this is a Spot Instance or a Scheduled Instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "instanceState",
|
||||
"type": "json",
|
||||
"desc": "The current state of the instance.",
|
||||
"example": {
|
||||
"code": "16",
|
||||
"name": "running"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "instanceType",
|
||||
"type": "文本",
|
||||
"desc": "The instance type.",
|
||||
"example": "t2.micro"
|
||||
},
|
||||
{
|
||||
"name": "ipAddress",
|
||||
"type": "文本",
|
||||
"desc": "The public IPv4 address, or the Carrier IP address assigned to the instance, if applicable.",
|
||||
"example": "54.194.252.215"
|
||||
},
|
||||
{
|
||||
"name": "ipv6Address",
|
||||
"type": "文本",
|
||||
"desc": "The IPv6 address assigned to the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "kernelId",
|
||||
"type": "文本",
|
||||
"desc": "The kernel associated with this instance, if applicable.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "keyName",
|
||||
"type": "文本",
|
||||
"desc": "The name of the key pair, if this instance was launched with an associated key pair.",
|
||||
"example": "my_keypair"
|
||||
},
|
||||
{
|
||||
"name": "launchTime",
|
||||
"type": "Time",
|
||||
"desc": "The time the instance was launched.",
|
||||
"example": "2018-05-08T16:46:19.000Z"
|
||||
},
|
||||
{
|
||||
"name": "licenseSet",
|
||||
"type": "json",
|
||||
"desc": "The license configurations for the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "maintenanceOptions",
|
||||
"type": "json",
|
||||
"desc": "Provides information on the recovery and maintenance options of your instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "metadataOptions",
|
||||
"type": "json",
|
||||
"desc": "The metadata options for the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "monitoring",
|
||||
"type": "json",
|
||||
"desc": "The monitoring for the instance.",
|
||||
"example": {
|
||||
"state": "disabled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "networkInterfaceSet",
|
||||
"type": "json",
|
||||
"desc": "The network interfaces for the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"networkInterfaceId": "eni-551ba033",
|
||||
"subnetId": "subnet-56f5f633",
|
||||
"vpcId": "vpc-11112222",
|
||||
"description": "Primary network interface",
|
||||
"ownerId": "123456789012",
|
||||
"status": "in-use",
|
||||
"macAddress": "02:dd:2c:5e:01:69",
|
||||
"privateIpAddress": "192.168.1.88",
|
||||
"privateDnsName": "ip-192-168-1-88.eu-west-1.compute.internal",
|
||||
"sourceDestCheck": "true",
|
||||
"groupSet": {
|
||||
"item": {
|
||||
"groupId": "sg-e4076980",
|
||||
"groupName": "SecurityGroup1"
|
||||
}
|
||||
},
|
||||
"attachment": {
|
||||
"attachmentId": "eni-attach-39697adc",
|
||||
"deviceIndex": "0",
|
||||
"status": "attached",
|
||||
"attachTime": "2018-05-08T16:46:19.000Z",
|
||||
"deleteOnTermination": "true"
|
||||
},
|
||||
"association": {
|
||||
"publicIp": "54.194.252.215",
|
||||
"publicDnsName": "ec2-54-194-252-215.eu-west-1.compute.amazonaws.com",
|
||||
"ipOwnerId": "amazon"
|
||||
},
|
||||
"privateIpAddressesSet": {
|
||||
"item": {
|
||||
"privateIpAddress": "192.168.1.88",
|
||||
"privateDnsName": "ip-192-168-1-88.eu-west-1.compute.internal",
|
||||
"primary": "true",
|
||||
"association": {
|
||||
"publicIp": "54.194.252.215",
|
||||
"publicDnsName": "ec2-54-194-252-215.eu-west-1.compute.amazonaws.com",
|
||||
"ipOwnerId": "amazon"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ipv6AddressesSet": {
|
||||
"item": {
|
||||
"ipv6Address": "2001:db8:1234:1a2b::123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "outpostArn",
|
||||
"type": "文本",
|
||||
"desc": "The Amazon Resource Name (ARN) of the Outpost.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "placement",
|
||||
"type": "json",
|
||||
"desc": "The location where the instance launched, if applicable.",
|
||||
"example": {
|
||||
"availabilityZone": "eu-west-1c",
|
||||
"groupName": null,
|
||||
"tenancy": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"type": "文本",
|
||||
"desc": "The value is Windows for Windows instances; otherwise blank.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "platformDetails",
|
||||
"type": "文本",
|
||||
"desc": "The platform details value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "privateDnsName",
|
||||
"type": "文本",
|
||||
"desc": "[IPv4 only] The private DNS hostname name assigned to the instance. This DNS hostname can only be used inside the Amazon EC2 network. This name is not available until the instance enters the running state.",
|
||||
"example": "ip-192-168-1-88.eu-west-1.compute.internal"
|
||||
},
|
||||
{
|
||||
"name": "privateDnsNameOptions",
|
||||
"type": "json",
|
||||
"desc": "The options for the instance hostname.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "privateIpAddress",
|
||||
"type": "文本",
|
||||
"desc": "The private IPv4 address assigned to the instance.",
|
||||
"example": "192.168.1.88"
|
||||
},
|
||||
{
|
||||
"name": "productCodes",
|
||||
"type": "json",
|
||||
"desc": "The product codes attached to this instance, if applicable.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "ramdiskId",
|
||||
"type": "文本",
|
||||
"desc": "The RAM disk associated with this instance, if applicable.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"type": "文本",
|
||||
"desc": "The reason for the most recent state transition. This might be an empty string.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "rootDeviceName",
|
||||
"type": "文本",
|
||||
"desc": "The device name of the root device volume (for example, /dev/sda1).",
|
||||
"example": "/dev/xvda"
|
||||
},
|
||||
{
|
||||
"name": "rootDeviceType",
|
||||
"type": "文本",
|
||||
"desc": "The root device type used by the AMI. The AMI can use an EBS volume or an instance store volume.",
|
||||
"example": "ebs"
|
||||
},
|
||||
{
|
||||
"name": "sourceDestCheck",
|
||||
"type": "Boolean",
|
||||
"desc": "Indicates whether source/destination checking is enabled.",
|
||||
"example": "true"
|
||||
},
|
||||
{
|
||||
"name": "spotInstanceRequestId",
|
||||
"type": "文本",
|
||||
"desc": "If the request is a Spot Instance request, the ID of the request.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "sriovNetSupport",
|
||||
"type": "文本",
|
||||
"desc": "Specifies whether enhanced networking with the Intel 82599 Virtual Function interface is enabled.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "stateReason",
|
||||
"type": "json",
|
||||
"desc": "The reason for the most recent state transition.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "subnetId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the subnet in which the instance is running.",
|
||||
"example": "subnet-56f5f633"
|
||||
},
|
||||
{
|
||||
"name": "tagSet",
|
||||
"type": "json",
|
||||
"desc": "Any tags assigned to the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"key": "Name",
|
||||
"value": "Server_1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tpmSupport",
|
||||
"type": "文本",
|
||||
"desc": "If the instance is configured for NitroTPM support, the value is v2.0. For more information, see NitroTPM in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "usageOperation",
|
||||
"type": "文本",
|
||||
"desc": "The usage operation value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "usageOperationUpdateTime",
|
||||
"type": "Time",
|
||||
"desc": "The time that the usage operation was last updated.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "virtualizationType",
|
||||
"type": "文本",
|
||||
"desc": "The virtualization type of the instance.",
|
||||
"example": "hvm"
|
||||
},
|
||||
{
|
||||
"name": "vpcId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the VPC in which the instance is running.",
|
||||
"example": "vpc-11112222"
|
||||
}
|
||||
[
|
||||
{
|
||||
"name": "amiLaunchIndex",
|
||||
"type": "Integer",
|
||||
"desc": "The AMI launch index, which can be used to find this instance in the launch group.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "architecture",
|
||||
"type": "String",
|
||||
"desc": "The architecture of the image.",
|
||||
"example": "i386"
|
||||
},
|
||||
{
|
||||
"name": "blockDeviceMapping",
|
||||
"type": "Array of InstanceBlockDeviceMapping objects",
|
||||
"desc": "Any block device mapping entries for the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "bootMode",
|
||||
"type": "String",
|
||||
"desc": "The boot mode that was specified by the AMI. If the value is uefi-preferred, the AMI supports both UEFI and Legacy BIOS. The currentInstanceBootMode parameter is the boot mode that is used to boot the instance at launch or start. For more information, see Boot modes in the Amazon EC2 User Guide.",
|
||||
"example": "legacy-bios"
|
||||
},
|
||||
{
|
||||
"name": "capacityReservationId",
|
||||
"type": "String",
|
||||
"desc": "The ID of the Capacity Reservation.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "capacityReservationSpecification",
|
||||
"type": "CapacityReservationSpecificationResponse object",
|
||||
"desc": "Information about the Capacity Reservation targeting option.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "clientToken",
|
||||
"type": "String",
|
||||
"desc": "The idempotency token you provided when you launched the instance, if applicable.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "cpuOptions",
|
||||
"type": "CpuOptions object",
|
||||
"desc": "The CPU options for the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "currentInstanceBootMode",
|
||||
"type": "String",
|
||||
"desc": "The boot mode that is used to boot the instance at launch or start. For more information, see Boot modes in the Amazon EC2 User Guide.",
|
||||
"example": "legacy-bios"
|
||||
},
|
||||
{
|
||||
"name": "dnsName",
|
||||
"type": "String",
|
||||
"desc": "[IPv4 only] The public DNS name assigned to the instance. This name is not available until the instance enters the running state. This name is only available if you've enabled DNS hostnames for your VPC.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "ebsOptimized",
|
||||
"type": "Boolean",
|
||||
"desc": "Indicates whether the instance is optimized for Amazon EBS I/O. This optimization provides dedicated throughput to Amazon EBS and an optimized configuration stack to provide optimal I/O performance. This optimization isn't available with all instance types. Additional usage charges apply when using an EBS Optimized instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "elasticGpuAssociationSet",
|
||||
"type": "Array of ElasticGpuAssociation objects",
|
||||
"desc": "The Elastic GPU associated with the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "elasticInferenceAcceleratorAssociationSet",
|
||||
"type": "Array of ElasticInferenceAcceleratorAssociation objects",
|
||||
"desc": "The elastic inference accelerator associated with the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "enaSupport",
|
||||
"type": "Boolean",
|
||||
"desc": "Specifies whether enhanced networking with ENA is enabled.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "enclaveOptions",
|
||||
"type": "EnclaveOptions object",
|
||||
"desc": "Indicates whether the instance is enabled for AWS Nitro Enclaves.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "groupSet",
|
||||
"type": "Array of GroupIdentifier objects",
|
||||
"desc": "The security groups for the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "hibernationOptions",
|
||||
"type": "HibernationOptions object",
|
||||
"desc": "Indicates whether the instance is enabled for hibernation.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "hypervisor",
|
||||
"type": "String",
|
||||
"desc": "The hypervisor type of the instance. The value xen is used for both Xen and Nitro hypervisors.",
|
||||
"example": "ovm"
|
||||
},
|
||||
{
|
||||
"name": "iamInstanceProfile",
|
||||
"type": "IamInstanceProfile object",
|
||||
"desc": "The IAM instance profile associated with the instance, if applicable.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "imageId",
|
||||
"type": "String",
|
||||
"desc": "The ID of the AMI used to launch the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "instanceId",
|
||||
"type": "String",
|
||||
"desc": "The ID of the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "instanceLifecycle",
|
||||
"type": "String",
|
||||
"desc": "Indicates whether this is a Spot Instance or a Scheduled Instance.",
|
||||
"example": "spot"
|
||||
},
|
||||
{
|
||||
"name": "instanceState",
|
||||
"type": "InstanceState object",
|
||||
"desc": "The current state of the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "instanceType",
|
||||
"type": "String",
|
||||
"desc": "The instance type.",
|
||||
"example": "a1.medium"
|
||||
},
|
||||
{
|
||||
"name": "ipAddress",
|
||||
"type": "String",
|
||||
"desc": "The public IPv4 address, or the Carrier IP address assigned to the instance, if applicable. A Carrier IP address only applies to an instance launched in a subnet associated with a Wavelength Zone.",
|
||||
"example": "Required: No"
|
||||
},
|
||||
{
|
||||
"name": "ipv6Address",
|
||||
"type": "String",
|
||||
"desc": "The IPv6 address assigned to the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "kernelId",
|
||||
"type": "String",
|
||||
"desc": "The kernel associated with this instance, if applicable.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "keyName",
|
||||
"type": "String",
|
||||
"desc": "The name of the key pair, if this instance was launched with an associated key pair.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "launchTime",
|
||||
"type": "Timestamp",
|
||||
"desc": "The time the instance was launched.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "licenseSet",
|
||||
"type": "Array of LicenseConfiguration objects",
|
||||
"desc": "The license configurations for the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "maintenanceOptions",
|
||||
"type": "InstanceMaintenanceOptions object",
|
||||
"desc": "Provides information on the recovery and maintenance options of your instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "metadataOptions",
|
||||
"type": "InstanceMetadataOptionsResponse object",
|
||||
"desc": "The metadata options for the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "monitoring",
|
||||
"type": "Monitoring object",
|
||||
"desc": "The monitoring for the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "networkInterfaceSet",
|
||||
"type": "Array of InstanceNetworkInterface objects",
|
||||
"desc": "The network interfaces for the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "outpostArn",
|
||||
"type": "String",
|
||||
"desc": "The Amazon Resource Name (ARN) of the Outpost.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "placement",
|
||||
"type": "Placement object",
|
||||
"desc": "The location where the instance launched, if applicable.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"type": "String",
|
||||
"desc": "The platform. This value is windows for Windows instances; otherwise, it is empty.",
|
||||
"example": "windows"
|
||||
},
|
||||
{
|
||||
"name": "platformDetails",
|
||||
"type": "String",
|
||||
"desc": "The platform details value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "privateDnsName",
|
||||
"type": "String",
|
||||
"desc": "[IPv4 only] The private DNS hostname name assigned to the instance. This DNS hostname can only be used inside the Amazon EC2 network. This name is not available until the instance enters the running state. The Amazon-provided DNS server resolves Amazon-provided private DNS hostnames if you've enabled DNS resolution and DNS hostnames in your VPC. If you are not using the Amazon-provided DNS server in your VPC, your custom domain name servers must resolve the hostname as appropriate.",
|
||||
"example": "Required: No"
|
||||
},
|
||||
{
|
||||
"name": "privateDnsNameOptions",
|
||||
"type": "PrivateDnsNameOptionsResponse object",
|
||||
"desc": "The options for the instance hostname.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "privateIpAddress",
|
||||
"type": "String",
|
||||
"desc": "The private IPv4 address assigned to the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "productCodes",
|
||||
"type": "Array of ProductCode objects",
|
||||
"desc": "The product codes attached to this instance, if applicable.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "ramdiskId",
|
||||
"type": "String",
|
||||
"desc": "The RAM disk associated with this instance, if applicable.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"type": "String",
|
||||
"desc": "The reason for the most recent state transition. This might be an empty string.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "rootDeviceName",
|
||||
"type": "String",
|
||||
"desc": "The device name of the root device volume (for example, /dev/sda1).",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "rootDeviceType",
|
||||
"type": "String",
|
||||
"desc": "The root device type used by the AMI. The AMI can use an EBS volume or an instance store volume.",
|
||||
"example": "ebs"
|
||||
},
|
||||
{
|
||||
"name": "sourceDestCheck",
|
||||
"type": "Boolean",
|
||||
"desc": "Indicates whether source/destination checking is enabled.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "spotInstanceRequestId",
|
||||
"type": "String",
|
||||
"desc": "If the request is a Spot Instance request, the ID of the request.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "sriovNetSupport",
|
||||
"type": "String",
|
||||
"desc": "Specifies whether enhanced networking with the Intel 82599 Virtual Function interface is enabled.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "stateReason",
|
||||
"type": "StateReason object",
|
||||
"desc": "The reason for the most recent state transition.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "subnetId",
|
||||
"type": "String",
|
||||
"desc": "The ID of the subnet in which the instance is running.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "tagSet",
|
||||
"type": "Array of Tag objects",
|
||||
"desc": "Any tags assigned to the instance.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "tpmSupport",
|
||||
"type": "String",
|
||||
"desc": "If the instance is configured for NitroTPM support, the value is v2.0. For more information, see NitroTPM in the Amazon EC2 User Guide.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "usageOperation",
|
||||
"type": "String",
|
||||
"desc": "The usage operation value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "usageOperationUpdateTime",
|
||||
"type": "Timestamp",
|
||||
"desc": "The time that the usage operation was last updated.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "virtualizationType",
|
||||
"type": "String",
|
||||
"desc": "The virtualization type of the instance.",
|
||||
"example": "hvm"
|
||||
},
|
||||
{
|
||||
"name": "vpcId",
|
||||
"type": "String",
|
||||
"desc": "The ID of the VPC in which the instance is running.",
|
||||
"example": ""
|
||||
}
|
||||
]
|
@@ -1,292 +1,284 @@
|
||||
[
|
||||
{
|
||||
"name": "status",
|
||||
"type": "文本",
|
||||
"example": "ACTIVE",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u72b6\u6001\u3002\n\n\u53d6\u503c\u8303\u56f4:\n\nACTIVE\u3001BUILD\u3001DELETED\u3001ERROR\u3001HARD_REBOOT\u3001MIGRATING\u3001PAUSED\u3001REBOOT\u3001REBUILD\u3001RESIZE\u3001REVERT_RESIZE\u3001SHUTOFF\u3001SHELVED\u3001SHELVED_OFFLOADED\u3001SOFT_DELETED\u3001SUSPENDED\u3001VERIFY_RESIZE\n\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u72b6\u6001\u8bf4\u660e\u8bf7\u53c2\u8003[\u4e91\u670d\u52a1\u5668\u72b6\u6001](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)"
|
||||
},
|
||||
{
|
||||
"name": "updated",
|
||||
"type": "文本",
|
||||
"example": "2019-05-22T03:30:52Z",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u66f4\u65b0\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:30:52Z"
|
||||
},
|
||||
{
|
||||
"name": "auto_terminate_time",
|
||||
"type": "文本",
|
||||
"example": "2020-01-19T03:30:52Z",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u81ea\u52a8\u91ca\u653e\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2020-01-19T03:30:52Z"
|
||||
},
|
||||
{
|
||||
"name": "hostId",
|
||||
"type": "文本",
|
||||
"example": "c7145889b2e3202cd295ceddb1742ff8941b827b586861fd0acedf64",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u4e3b\u673a\u7684\u4e3b\u673aID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:host",
|
||||
"type": "文本",
|
||||
"example": "pod01.cn-north-1c",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u4e3b\u673a\u7684\u4e3b\u673a\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "addresses",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u7f51\u7edc\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "key_name",
|
||||
"type": "文本",
|
||||
"example": "KeyPair-test",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u4f7f\u7528\u7684\u5bc6\u94a5\u5bf9\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u955c\u50cf\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:task_state",
|
||||
"type": "文本",
|
||||
"example": "rebooting",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5f53\u524d\u4efb\u52a1\u7684\u72b6\u6001\u3002\n\n\u53d6\u503c\u8303\u56f4\u8bf7\u53c2\u8003[\u4e91\u670d\u52a1\u5668\u72b6\u6001](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)\u88683\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:vm_state",
|
||||
"type": "文本",
|
||||
"example": "active",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5f53\u524d\u72b6\u6001\u3002\n\n\u4e91\u670d\u52a1\u5668\u72b6\u6001\u8bf4\u660e\u8bf7\u53c2\u8003[\u4e91\u670d\u52a1\u5668\u72b6\u6001](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:instance_name",
|
||||
"type": "文本",
|
||||
"example": "instance-0048a91b",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u522b\u540d\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:hypervisor_hostname",
|
||||
"type": "文本",
|
||||
"example": "nova022@36",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u865a\u62df\u5316\u4e3b\u673a\u540d\u3002"
|
||||
},
|
||||
{
|
||||
"name": "flavor",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u89c4\u683c\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"type": "文本",
|
||||
"example": "4f4b3dfa-eb70-47cf-a60a-998a53bd6666",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668ID,\u683c\u5f0f\u4e3aUUID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "security_groups",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"$ref": "#/definitions/ServerSecurityGroup"
|
||||
},
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u5b89\u5168\u7ec4\u5217\u8868\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-AZ:availability_zone",
|
||||
"type": "文本",
|
||||
"example": "cn-north-1c",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u53ef\u7528\u533a\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"type": "文本",
|
||||
"example": "05498fe56b8010d41f7fc01e280b6666",
|
||||
"desc": "\u521b\u5efa\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u7528\u6237ID,\u683c\u5f0f\u4e3aUUID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "文本",
|
||||
"example": "ecs-test-server",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"type": "文本",
|
||||
"example": "2017-07-15T11:30:52Z",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u521b\u5efa\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:19:19Z"
|
||||
},
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"type": "文本",
|
||||
"example": "743b4c0428d94531b9f2add666646666",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u79df\u6237ID,\u5373\u9879\u76eeid,\u548cproject_id\u8868\u793a\u76f8\u540c\u7684\u6982\u5ff5,\u683c\u5f0f\u4e3aUUID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-DCF:diskConfig",
|
||||
"type": "文本",
|
||||
"example": "AUTO",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027, diskConfig\u7684\u7c7b\u578b\u3002\n\n- MANUAL,\u955c\u50cf\u7a7a\u95f4\u4e0d\u4f1a\u6269\u5c55\u3002\n- AUTO,\u7cfb\u7edf\u76d8\u955c\u50cf\u7a7a\u95f4\u4f1a\u81ea\u52a8\u6269\u5c55\u4e3a\u4e0eflavor\u5927\u5c0f\u4e00\u81f4\u3002"
|
||||
},
|
||||
{
|
||||
"name": "accessIPv4",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u9884\u7559\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "accessIPv6",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u9884\u7559\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "fault",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6545\u969c\u4fe1\u606f\u3002\n\n\u53ef\u9009\u53c2\u6570,\u5728\u5f39\u6027\u4e91\u670d\u52a1\u5668\u72b6\u6001\u4e3aERROR\u4e14\u5b58\u5728\u5f02\u5e38\u7684\u60c5\u51b5\u4e0b\u8fd4\u56de\u3002"
|
||||
},
|
||||
{
|
||||
"name": "progress",
|
||||
"type": "整数",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u8fdb\u5ea6\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:power_state",
|
||||
"type": "整数",
|
||||
"example": 4,
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7535\u6e90\u72b6\u6001\u3002"
|
||||
},
|
||||
{
|
||||
"name": "config_drive",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "config drive\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "metadata",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5143\u6570\u636e\u3002\n\n> \u8bf4\u660e:\n> \n> \u5143\u6570\u636e\u5305\u542b\u7cfb\u7edf\u9ed8\u8ba4\u6dfb\u52a0\u5b57\u6bb5\u548c\u7528\u6237\u8bbe\u7f6e\u7684\u5b57\u6bb5\u3002\n\n\u7cfb\u7edf\u9ed8\u8ba4\u6dfb\u52a0\u5b57\u6bb5\n\n1. charging_mode\n\u4e91\u670d\u52a1\u5668\u7684\u8ba1\u8d39\u7c7b\u578b\u3002\n\n- \u201c0\u201d:\u6309\u9700\u8ba1\u8d39(\u5373postPaid-\u540e\u4ed8\u8d39\u65b9\u5f0f)\u3002\n- \u201c1\u201d:\u6309\u5305\u5e74\u5305\u6708\u8ba1\u8d39(\u5373prePaid-\u9884\u4ed8\u8d39\u65b9\u5f0f)\u3002\"2\":\u7ade\u4ef7\u5b9e\u4f8b\u8ba1\u8d39\n\n2. metering.order_id\n\u6309\u201c\u5305\u5e74/\u5305\u6708\u201d\u8ba1\u8d39\u7684\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u8ba2\u5355ID\u3002\n\n3. metering.product_id\n\u6309\u201c\u5305\u5e74/\u5305\u6708\u201d\u8ba1\u8d39\u7684\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u4ea7\u54c1ID\u3002\n\n4. vpc_id\n\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u7684\u865a\u62df\u79c1\u6709\u4e91ID\u3002\n\n5. EcmResStatus\n\u4e91\u670d\u52a1\u5668\u7684\u51bb\u7ed3\u72b6\u6001\u3002\n\n- normal:\u4e91\u670d\u52a1\u5668\u6b63\u5e38\u72b6\u6001(\u672a\u88ab\u51bb\u7ed3)\u3002\n- freeze:\u4e91\u670d\u52a1\u5668\u88ab\u51bb\u7ed3\u3002\n\n> \u5f53\u4e91\u670d\u52a1\u5668\u88ab\u51bb\u7ed3\u6216\u8005\u89e3\u51bb\u540e,\u7cfb\u7edf\u9ed8\u8ba4\u6dfb\u52a0\u8be5\u5b57\u6bb5,\u4e14\u8be5\u5b57\u6bb5\u5fc5\u9009\u3002\n\n6. metering.image_id\n\u4e91\u670d\u52a1\u5668\u64cd\u4f5c\u7cfb\u7edf\u5bf9\u5e94\u7684\u955c\u50cfID\n\n7. metering.imagetype\n\u955c\u50cf\u7c7b\u578b,\u76ee\u524d\u652f\u6301:\n\n- \u516c\u5171\u955c\u50cf(gold)\n- \u79c1\u6709\u955c\u50cf(private)\n- \u5171\u4eab\u955c\u50cf(shared)\n\n8. metering.resourcespeccode\n\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u8d44\u6e90\u89c4\u683c\u3002\n\n9. image_name\n\u4e91\u670d\u52a1\u5668\u64cd\u4f5c\u7cfb\u7edf\u5bf9\u5e94\u7684\u955c\u50cf\u540d\u79f0\u3002\n\n10. os_bit\n\u64cd\u4f5c\u7cfb\u7edf\u4f4d\u6570,\u4e00\u822c\u53d6\u503c\u4e3a\u201c32\u201d\u6216\u8005\u201c64\u201d\u3002\n\n11. lockCheckEndpoint\n\u56de\u8c03URL,\u7528\u4e8e\u68c0\u67e5\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u52a0\u9501\u662f\u5426\u6709\u6548\u3002\n\n- \u5982\u679c\u6709\u6548,\u5219\u4e91\u670d\u52a1\u5668\u4fdd\u6301\u9501\u5b9a\u72b6\u6001\u3002\n- \u5982\u679c\u65e0\u6548,\u89e3\u9664\u9501\u5b9a\u72b6\u6001,\u5220\u9664\u5931\u6548\u7684\u9501\u3002\n\n12. lockSource\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6765\u81ea\u54ea\u4e2a\u670d\u52a1\u3002\u8ba2\u5355\u52a0\u9501(ORDER)\n\n13. lockSourceId\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u52a0\u9501\u6765\u81ea\u54ea\u4e2aID\u3002lockSource\u4e3a\u201cORDER\u201d\u65f6,lockSourceId\u4e3a\u8ba2\u5355ID\u3002\n\n14. lockScene\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u52a0\u9501\u7c7b\u578b\u3002\n\n- \u6309\u9700\u8f6c\u5305\u5468\u671f(TO_PERIOD_LOCK)\n\n15. virtual_env_type\n\n- IOS\u955c\u50cf\u521b\u5efa\u865a\u62df\u673a,\"virtual_env_type\": \"IsoImage\" \u5c5e\u6027;\n- \u975eIOS\u955c\u50cf\u521b\u5efa\u865a\u62df\u673a,\u572819.5.0\u7248\u672c\u4ee5\u540e\u521b\u5efa\u7684\u865a\u62df\u673a\u5c06\u4e0d\u4f1a\u6dfb\u52a0virtual_env_type \u5c5e\u6027,\u800c\u5728\u6b64\u4e4b\u524d\u7684\u7248\u672c\u521b\u5efa\u7684\u865a\u62df\u673a\u53ef\u80fd\u4f1a\u8fd4\u56de\"virtual_env_type\": \"FusionCompute\"\u5c5e\u6027 \u3002\n\n> virtual_env_type\u5c5e\u6027\u4e0d\u5141\u8bb8\u7528\u6237\u589e\u52a0\u3001\u5220\u9664\u548c\u4fee\u6539\u3002\n\n16. metering.resourcetype\n\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u8d44\u6e90\u7c7b\u578b\u3002\n\n17. os_type\n\u64cd\u4f5c\u7cfb\u7edf\u7c7b\u578b,\u53d6\u503c\u4e3a:Linux\u3001Windows\u3002\n\n18. cascaded.instance_extrainfo\n\u7cfb\u7edf\u5185\u90e8\u865a\u62df\u673a\u6269\u5c55\u4fe1\u606f\u3002\n\n19. __support_agent_list\n\u4e91\u670d\u52a1\u5668\u662f\u5426\u652f\u6301\u4f01\u4e1a\u4e3b\u673a\u5b89\u5168\u3001\u4e3b\u673a\u76d1\u63a7\u3002\n\n- \u201chss\u201d:\u4f01\u4e1a\u4e3b\u673a\u5b89\u5168\n- \u201cces\u201d:\u4e3b\u673a\u76d1\u63a7\n\n20. agency_name\n\u59d4\u6258\u7684\u540d\u79f0\u3002\n\n\u59d4\u6258\u662f\u7531\u79df\u6237\u7ba1\u7406\u5458\u5728\u7edf\u4e00\u8eab\u4efd\u8ba4\u8bc1\u670d\u52a1(Identity and Access Management,IAM)\u4e0a\u521b\u5efa\u7684,\u53ef\u4ee5\u4e3a\u5f39\u6027\u4e91\u670d\u52a1\u5668\u63d0\u4f9b\u8bbf\u95ee\u4e91\u670d\u52a1\u7684\u4e34\u65f6\u51ed\u8bc1\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-SRV-USG:launched_at",
|
||||
"type": "文本",
|
||||
"example": "2018-08-15T14:21:22.000000",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u542f\u52a8\u65f6\u95f4\u3002\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:23:59.000000"
|
||||
},
|
||||
{
|
||||
"name": "OS-SRV-USG:terminated_at",
|
||||
"type": "文本",
|
||||
"example": "2019-05-22T03:23:59.000000",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5220\u9664\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:23:59.000000"
|
||||
},
|
||||
{
|
||||
"name": "os-extended-volumes:volumes_attached",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"$ref": "#/definitions/ServerExtendVolumeAttachment"
|
||||
},
|
||||
"desc": "\u6302\u8f7d\u5230\u5f39\u6027\u4e91\u670d\u52a1\u5668\u4e0a\u7684\u78c1\u76d8\u3002"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "文本",
|
||||
"example": "ecs description",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u63cf\u8ff0\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "host_status",
|
||||
"type": "文本",
|
||||
"example": "UP",
|
||||
"desc": "nova-compute\u72b6\u6001\u3002\n\n- UP:\u670d\u52a1\u6b63\u5e38\n- UNKNOWN:\u72b6\u6001\u672a\u77e5\n- DOWN:\u670d\u52a1\u5f02\u5e38\n- MAINTENANCE:\u7ef4\u62a4\u72b6\u6001\n- \u7a7a\u5b57\u7b26\u4e32:\u5f39\u6027\u4e91\u670d\u52a1\u5668\u65e0\u4e3b\u673a\u4fe1\u606f"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:hostname",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u4e3b\u673a\u540d\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:reservation_id",
|
||||
"type": "文本",
|
||||
"example": "r-f06p3js8",
|
||||
"desc": "\u6279\u91cf\u521b\u5efa\u573a\u666f,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u9884\u7559ID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:launch_index",
|
||||
"type": "整数",
|
||||
"example": null,
|
||||
"desc": "\u6279\u91cf\u521b\u5efa\u573a\u666f,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u542f\u52a8\u987a\u5e8f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:kernel_id",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u82e5\u4f7f\u7528AMI\u683c\u5f0f\u7684\u955c\u50cf,\u5219\u8868\u793akernel image\u7684UUID;\u5426\u5219,\u7559\u7a7a\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:ramdisk_id",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u82e5\u4f7f\u7528AMI\u683c\u5f0f\u955c\u50cf,\u5219\u8868\u793aramdisk image\u7684UUID;\u5426\u5219,\u7559\u7a7a\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:root_device_name",
|
||||
"type": "文本",
|
||||
"example": "/dev/vda",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7cfb\u7edf\u76d8\u7684\u8bbe\u5907\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:user_data",
|
||||
"type": "文本",
|
||||
"example": "IyEvYmluL2Jhc2gKZWNobyAncm9vdDokNiRjcGRkSjckWm5WZHNiR253Z0l0SGlxUjZxbWtLTlJaeU9lZUtKd3dPbG9XSFdUeGFzWjA1STYwdnJYRTdTUTZGbEpFbWlXZ21WNGNmZ1pac1laN1BkMTBLRndyeC8nIHwgY2hwYXNzd2Q6666",
|
||||
"desc": "\u521b\u5efa\u5f39\u6027\u4e91\u670d\u52a1\u5668\u65f6\u6307\u5b9a\u7684user_data\u3002"
|
||||
},
|
||||
{
|
||||
"name": "locked",
|
||||
"type": "boolean",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u662f\u5426\u4e3a\u9501\u5b9a\u72b6\u6001\u3002\n\n- true:\u9501\u5b9a\n- false:\u672a\u9501\u5b9a"
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"type": "文本"
|
||||
},
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6807\u7b7e\u3002"
|
||||
},
|
||||
{
|
||||
"name": "os:scheduler_hints",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u8c03\u5ea6\u4fe1\u606f"
|
||||
},
|
||||
{
|
||||
"name": "enterprise_project_id",
|
||||
"type": "文本",
|
||||
"example": "0",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u7684\u4f01\u4e1a\u9879\u76eeID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "sys_tags",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"$ref": "#/definitions/ServerSystemTag"
|
||||
},
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7cfb\u7edf\u6807\u7b7e\u3002"
|
||||
},
|
||||
{
|
||||
"name": "cpu_options",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u81ea\u5b9a\u4e49CPU\u9009\u9879\u3002"
|
||||
},
|
||||
{
|
||||
"name": "hypervisor",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "hypervisor\u4fe1\u606f\u3002"
|
||||
}
|
||||
[
|
||||
{
|
||||
"name": "status",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器状态。\n\n取值范围:\n\nACTIVE、BUILD、DELETED、ERROR、HARD_REBOOT、MIGRATING、PAUSED、REBOOT、REBUILD、RESIZE、REVERT_RESIZE、SHUTOFF、SHELVED、SHELVED_OFFLOADED、SOFT_DELETED、SUSPENDED、VERIFY_RESIZE\n\n弹性云服务器状态说明请参考[云服务器状态](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)",
|
||||
"example": "ACTIVE"
|
||||
},
|
||||
{
|
||||
"name": "updated",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器更新时间。\n\n时间格式例如:2019-05-22T03:30:52Z",
|
||||
"example": "2019-05-22T03:30:52Z"
|
||||
},
|
||||
{
|
||||
"name": "auto_terminate_time",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器定时删除时间。\n\n时间格式例如:2020-01-19T03:30:52Z",
|
||||
"example": "2020-01-19T03:30:52Z"
|
||||
},
|
||||
{
|
||||
"name": "hostId",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器所在主机的主机ID。",
|
||||
"example": "c7145889b2e3202cd295ceddb1742ff8941b827b586861fd0acedf64"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:host",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器所在主机的主机名称。",
|
||||
"example": "pod01.cn-north-1c"
|
||||
},
|
||||
{
|
||||
"name": "addresses",
|
||||
"type": "object",
|
||||
"desc": "弹性云服务器的网络属性。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "key_name",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器使用的密钥对名称。",
|
||||
"example": "KeyPair-test"
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "",
|
||||
"desc": "弹性云服务器镜像信息。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:task_state",
|
||||
"type": "string",
|
||||
"desc": "扩展属性,弹性云服务器当前任务的状态。\n\n取值范围请参考[云服务器状态](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)表3。",
|
||||
"example": "rebooting"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:vm_state",
|
||||
"type": "string",
|
||||
"desc": "扩展属性,弹性云服务器当前状态。\n\n云服务器状态说明请参考[云服务器状态](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)。",
|
||||
"example": "active"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:instance_name",
|
||||
"type": "string",
|
||||
"desc": "扩展属性,弹性云服务器别名。",
|
||||
"example": "instance-0048a91b"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:hypervisor_hostname",
|
||||
"type": "string",
|
||||
"desc": "扩展属性,弹性云服务器所在虚拟化主机名。",
|
||||
"example": "nova022@36"
|
||||
},
|
||||
{
|
||||
"name": "flavor",
|
||||
"type": "",
|
||||
"desc": "弹性云服务器规格信息。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器ID,格式为UUID。",
|
||||
"example": "4f4b3dfa-eb70-47cf-a60a-998a53bd6666"
|
||||
},
|
||||
{
|
||||
"name": "security_groups",
|
||||
"type": "array",
|
||||
"desc": "弹性云服务器所属安全组列表。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-AZ:availability_zone",
|
||||
"type": "string",
|
||||
"desc": "扩展属性,弹性云服务器所在可用区名称。",
|
||||
"example": "cn-north-1c"
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"type": "string",
|
||||
"desc": "创建弹性云服务器的用户ID,格式为UUID。",
|
||||
"example": "05498fe56b8010d41f7fc01e280b6666"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器名称。",
|
||||
"example": "ecs-test-server"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器创建时间。\n\n时间格式例如:2019-05-22T03:19:19Z",
|
||||
"example": "2017-07-15T11:30:52Z"
|
||||
},
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器所属租户ID,即项目id,和project_id表示相同的概念,格式为UUID。",
|
||||
"example": "743b4c0428d94531b9f2add666646666"
|
||||
},
|
||||
{
|
||||
"name": "OS-DCF:diskConfig",
|
||||
"type": "string",
|
||||
"desc": "扩展属性, diskConfig的类型。\n\n- MANUAL,镜像空间不会扩展。\n- AUTO,系统盘镜像空间会自动扩展为与flavor大小一致。",
|
||||
"example": "AUTO"
|
||||
},
|
||||
{
|
||||
"name": "accessIPv4",
|
||||
"type": "string",
|
||||
"desc": "预留属性。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "accessIPv6",
|
||||
"type": "string",
|
||||
"desc": "预留属性。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "fault",
|
||||
"type": "",
|
||||
"desc": "弹性云服务器故障信息。\n\n可选参数,在弹性云服务器状态为ERROR且存在异常的情况下返回。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "progress",
|
||||
"type": "integer",
|
||||
"desc": "弹性云服务器进度。",
|
||||
"example": 0
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:power_state",
|
||||
"type": "integer",
|
||||
"desc": "扩展属性,弹性云服务器电源状态。",
|
||||
"example": 4
|
||||
},
|
||||
{
|
||||
"name": "config_drive",
|
||||
"type": "string",
|
||||
"desc": "config drive信息。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "metadata",
|
||||
"type": "object",
|
||||
"desc": "弹性云服务器元数据。\n\n> 说明:\n> \n> 元数据包含系统默认添加字段和用户设置的字段。\n\n系统默认添加字段\n\n1. charging_mode\n云服务器的计费类型。\n\n- “0”:按需计费(即postPaid-后付费方式)。\n- “1”:按包年包月计费(即prePaid-预付费方式)。\"2\":竞价实例计费\n\n2. metering.order_id\n按“包年/包月”计费的云服务器对应的订单ID。\n\n3. metering.product_id\n按“包年/包月”计费的云服务器对应的产品ID。\n\n4. vpc_id\n云服务器所属的虚拟私有云ID。\n\n5. EcmResStatus\n云服务器的冻结状态。\n\n- normal:云服务器正常状态(未被冻结)。\n- freeze:云服务器被冻结。\n\n> 当云服务器被冻结或者解冻后,系统默认添加该字段,且该字段必选。\n\n6. metering.image_id\n云服务器操作系统对应的镜像ID\n\n7. metering.imagetype\n镜像类型,目前支持:\n\n- 公共镜像(gold)\n- 私有镜像(private)\n- 共享镜像(shared)\n\n8. metering.resourcespeccode\n云服务器对应的资源规格。\n\n9. image_name\n云服务器操作系统对应的镜像名称。\n\n10. os_bit\n操作系统位数,一般取值为“32”或者“64”。\n\n11. lockCheckEndpoint\n回调URL,用于检查弹性云服务器的加锁是否有效。\n\n- 如果有效,则云服务器保持锁定状态。\n- 如果无效,解除锁定状态,删除失效的锁。\n\n12. lockSource\n弹性云服务器来自哪个服务。订单加锁(ORDER)\n\n13. lockSourceId\n弹性云服务器的加锁来自哪个ID。lockSource为“ORDER”时,lockSourceId为订单ID。\n\n14. lockScene\n弹性云服务器的加锁类型。\n\n- 按需转包周期(TO_PERIOD_LOCK)\n\n15. virtual_env_type\n\n- IOS镜像创建虚拟机,\"virtual_env_type\": \"IsoImage\" 属性;\n- 非IOS镜像创建虚拟机,在19.5.0版本以后创建的虚拟机将不会添加virtual_env_type 属性,而在此之前的版本创建的虚拟机可能会返回\"virtual_env_type\": \"FusionCompute\"属性 。\n\n> virtual_env_type属性不允许用户增加、删除和修改。\n\n16. metering.resourcetype\n云服务器对应的资源类型。\n\n17. os_type\n操作系统类型,取值为:Linux、Windows。\n\n18. cascaded.instance_extrainfo\n系统内部虚拟机扩展信息。\n\n19. __support_agent_list\n云服务器是否支持企业主机安全、主机监控。\n\n- “hss”:企业主机安全\n- “ces”:主机监控\n\n20. agency_name\n委托的名称。\n\n委托是由租户管理员在统一身份认证服务(Identity and Access Management,IAM)上创建的,可以为弹性云服务器提供访问云服务的临时凭证。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "OS-SRV-USG:launched_at",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器启动时间。时间格式例如:2019-05-22T03:23:59.000000",
|
||||
"example": "2018-08-15T14:21:22.000000"
|
||||
},
|
||||
{
|
||||
"name": "OS-SRV-USG:terminated_at",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器删除时间。\n\n时间格式例如:2019-05-22T03:23:59.000000",
|
||||
"example": "2019-05-22T03:23:59.000000"
|
||||
},
|
||||
{
|
||||
"name": "os-extended-volumes:volumes_attached",
|
||||
"type": "array",
|
||||
"desc": "挂载到弹性云服务器上的磁盘。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器的描述信息。",
|
||||
"example": "ecs description"
|
||||
},
|
||||
{
|
||||
"name": "host_status",
|
||||
"type": "string",
|
||||
"desc": "nova-compute状态。\n\n- UP:服务正常\n- UNKNOWN:状态未知\n- DOWN:服务异常\n- MAINTENANCE:维护状态\n- 空字符串:弹性云服务器无主机信息",
|
||||
"example": "UP"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:hostname",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器的主机名。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:reservation_id",
|
||||
"type": "string",
|
||||
"desc": "批量创建场景,弹性云服务器的预留ID。",
|
||||
"example": "r-f06p3js8"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:launch_index",
|
||||
"type": "integer",
|
||||
"desc": "批量创建场景,弹性云服务器的启动顺序。",
|
||||
"example": 0
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:kernel_id",
|
||||
"type": "string",
|
||||
"desc": "若使用AMI格式的镜像,则表示kernel image的UUID;否则,留空。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:ramdisk_id",
|
||||
"type": "string",
|
||||
"desc": "若使用AMI格式镜像,则表示ramdisk image的UUID;否则,留空。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:root_device_name",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器系统盘的设备名称。",
|
||||
"example": "/dev/vda"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:user_data",
|
||||
"type": "string",
|
||||
"desc": "创建弹性云服务器时指定的user_data。",
|
||||
"example": "IyEvYmluL2Jhc2gKZWNobyAncm9vdDokNiRjcGRkSjckWm5WZHNiR253Z0l0SGlxUjZxbWtLTlJaeU9lZUtKd3dPbG9XSFdUeGFzWjA1STYwdnJYRTdTUTZGbEpFbWlXZ21WNGNmZ1pac1laN1BkMTBLRndyeC8nIHwgY2hwYXNzd2Q6666"
|
||||
},
|
||||
{
|
||||
"name": "locked",
|
||||
"type": "boolean",
|
||||
"desc": "弹性云服务器是否为锁定状态。\n\n- true:锁定\n- false:未锁定",
|
||||
"example": false
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "array",
|
||||
"desc": "弹性云服务器标签。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "os:scheduler_hints",
|
||||
"type": "",
|
||||
"desc": "弹性云服务器调度信息",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "enterprise_project_id",
|
||||
"type": "string",
|
||||
"desc": "弹性云服务器所属的企业项目ID。",
|
||||
"example": "0"
|
||||
},
|
||||
{
|
||||
"name": "sys_tags",
|
||||
"type": "array",
|
||||
"desc": "弹性云服务器系统标签。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "cpu_options",
|
||||
"type": "",
|
||||
"desc": "自定义CPU选项。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "hypervisor",
|
||||
"type": "",
|
||||
"desc": "hypervisor信息。",
|
||||
"example": ""
|
||||
}
|
||||
]
|
@@ -1,297 +1,248 @@
|
||||
[
|
||||
{
|
||||
"name": "Placement",
|
||||
"type": "json",
|
||||
"desc": "实例所在的位置。",
|
||||
"example": {
|
||||
"HostId": "host-h3m57oik",
|
||||
"ProjectId": 1174660,
|
||||
"HostIds": [],
|
||||
"Zone": "ap-guangzhou-1",
|
||||
"HostIps": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "InstanceId",
|
||||
"type": "文本",
|
||||
"desc": "实例ID。",
|
||||
"example": "ins-xlsyru2j"
|
||||
},
|
||||
{
|
||||
"name": "InstanceType",
|
||||
"type": "文本",
|
||||
"desc": "实例机型。",
|
||||
"example": "S2.SMALL2"
|
||||
},
|
||||
{
|
||||
"name": "CPU",
|
||||
"type": "整数",
|
||||
"desc": "实例的CPU核数,单位:核。",
|
||||
"example": 1
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"type": "整数",
|
||||
"desc": "实例内存容量,单位:GB。",
|
||||
"example": 1
|
||||
},
|
||||
{
|
||||
"name": "RestrictState",
|
||||
"type": "文本",
|
||||
"desc": "实例业务状态。取值范围: NORMAL:表示正常状态的实例 EXPIRED:表示过期的实例 PROTECTIVELY_ISOLATED:表示被安全隔离的实例。",
|
||||
"example": "PROTECTIVELY_ISOLATED"
|
||||
},
|
||||
{
|
||||
"name": "InstanceName",
|
||||
"type": "文本",
|
||||
"desc": "实例名称。",
|
||||
"example": "test"
|
||||
},
|
||||
{
|
||||
"name": "InstanceChargeType",
|
||||
"type": "文本",
|
||||
"desc": "实例计费模式。取值范围: PREPAID:表示预付费,即包年包月 POSTPAID_BY_HOUR:表示后付费,即按量计费 CDHPAID:专用宿主机付费,即只对专用宿主机计费,不对专用宿主机上的实例计费。 SPOTPAID:表示竞价实例付费。",
|
||||
"example": "POSTPAID_BY_HOUR"
|
||||
},
|
||||
{
|
||||
"name": "SystemDisk",
|
||||
"type": "json",
|
||||
"desc": "实例系统盘信息。",
|
||||
"example": {
|
||||
"DiskSize": 50,
|
||||
"CdcId": null,
|
||||
"DiskId": "disk-czsodtl1",
|
||||
"DiskType": "CLOUD_SSD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DataDisks",
|
||||
"type": "json",
|
||||
"desc": "实例数据盘信息。",
|
||||
"example": [
|
||||
{
|
||||
"DeleteWithInstance": true,
|
||||
"Encrypt": true,
|
||||
"CdcId": null,
|
||||
"DiskType": "CLOUD_SSD",
|
||||
"ThroughputPerformance": 0,
|
||||
"KmsKeyId": null,
|
||||
"DiskSize": 50,
|
||||
"SnapshotId": null,
|
||||
"DiskId": "disk-bzsodtn1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PrivateIpAddresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例主网卡的内网IP列表。",
|
||||
"example": [
|
||||
"172.16.32.78"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PublicIpAddresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例主网卡的公网IP列表。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": [
|
||||
"123.207.11.190"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "InternetAccessible",
|
||||
"type": "json",
|
||||
"desc": "实例带宽信息。",
|
||||
"example": {
|
||||
"PublicIpAssigned": true,
|
||||
"InternetChargeType": "TRAFFIC_POSTPAID_BY_HOUR",
|
||||
"BandwidthPackageId": null,
|
||||
"InternetMaxBandwidthOut": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "VirtualPrivateCloud",
|
||||
"type": "json",
|
||||
"desc": "实例所属虚拟私有网络信息。",
|
||||
"example": {
|
||||
"SubnetId": "subnet-mv4sn55k",
|
||||
"AsVpcGateway": false,
|
||||
"Ipv6AddressCount": 1,
|
||||
"VpcId": "vpc-m0cnatxj",
|
||||
"PrivateIpAddresses": [
|
||||
"172.16.3.59"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ImageId",
|
||||
"type": "文本",
|
||||
"desc": "生产实例所使用的镜像ID。",
|
||||
"example": "img-8toqc6s3"
|
||||
},
|
||||
{
|
||||
"name": "RenewFlag",
|
||||
"type": "文本",
|
||||
"desc": "自动续费标识。取值范围: NOTIFY_AND_MANUAL_RENEW:表示通知即将过期,但不自动续费 NOTIFY_AND_AUTO_RENEW:表示通知即将过期,而且自动续费 DISABLE_NOTIFY_AND_MANUAL_RENEW:表示不通知即将过期,也不自动续费。 注意:后付费模式本项为null",
|
||||
"example": "NOTIFY_AND_MANUAL_RENEW"
|
||||
},
|
||||
{
|
||||
"name": "CreatedTime",
|
||||
"type": "json",
|
||||
"desc": "创建时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。",
|
||||
"example": "2020-09-22T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ExpiredTime",
|
||||
"type": "json",
|
||||
"desc": "到期时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。注意:后付费模式本项为null",
|
||||
"example": "2020-09-22T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "OsName",
|
||||
"type": "文本",
|
||||
"desc": "操作系统名称。",
|
||||
"example": "CentOS 7.4 64bit"
|
||||
},
|
||||
{
|
||||
"name": "SecurityGroupIds",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例所属安全组。该参数可以通过调用 DescribeSecurityGroups 的返回值中的sgId字段来获取。",
|
||||
"example": [
|
||||
"sg-p1ezv4wz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LoginSettings",
|
||||
"type": "json",
|
||||
"desc": "实例登录设置。目前只返回实例所关联的密钥。",
|
||||
"example": {
|
||||
"Password": "123qwe!@#QWE",
|
||||
"KeepImageLogin": "False",
|
||||
"KeyIds": [
|
||||
"skey-b4vakk62"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "InstanceState",
|
||||
"type": "文本",
|
||||
"desc": "实例状态。取值范围: PENDING:表示创建中 LAUNCH_FAILED:表示创建失败 RUNNING:表示运行中 STOPPED:表示关机 STARTING:表示开机中 STOPPING:表示关机中 REBOOTING:表示重启中 SHUTDOWN:表示停止待销毁 TERMINATING:表示销毁中。",
|
||||
"example": "RUNNING"
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"type": "json",
|
||||
"desc": "实例关联的标签列表。",
|
||||
"example": [
|
||||
{
|
||||
"Value": "test",
|
||||
"Key": "test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "StopChargingMode",
|
||||
"type": "文本",
|
||||
"desc": "实例的关机计费模式。 取值范围: KEEP_CHARGING:关机继续收费 STOP_CHARGING:关机停止收费NOT_APPLICABLE:实例处于非关机状态或者不适用关机停止计费的条件",
|
||||
"example": "NOT_APPLICABLE"
|
||||
},
|
||||
{
|
||||
"name": "Uuid",
|
||||
"type": "文本",
|
||||
"desc": "实例全局唯一ID",
|
||||
"example": "e85f1388-0422-410d-8e50-bef540e78c18"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperation",
|
||||
"type": "文本",
|
||||
"desc": "实例的最新操作。例:StopInstances、ResetInstance。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "ResetInstancesType"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationState",
|
||||
"type": "文本",
|
||||
"desc": "实例的最新操作状态。取值范围: SUCCESS:表示操作成功 OPERATING:表示操作执行中 FAILED:表示操作失败 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "SUCCESS"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationRequestId",
|
||||
"type": "文本",
|
||||
"desc": "实例最新操作的唯一请求 ID。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "c7de1287-061d-4ace-8caf-6ad8e5a2f29a"
|
||||
},
|
||||
{
|
||||
"name": "DisasterRecoverGroupId",
|
||||
"type": "文本",
|
||||
"desc": "分散置放群组ID。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "IPv6Addresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例的IPv6地址。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": [
|
||||
"2001:0db8:86a3:08d3:1319:8a2e:0370:7344"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CamRoleName",
|
||||
"type": "文本",
|
||||
"desc": "CAM角色名。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "HpcClusterId",
|
||||
"type": "文本",
|
||||
"desc": "高性能计算集群ID。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "RdmaIpAddresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "高性能计算集群IP列表。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": []
|
||||
},
|
||||
{
|
||||
"name": "IsolatedSource",
|
||||
"type": "文本",
|
||||
"desc": "实例隔离类型。取值范围: ARREAR:表示欠费隔离 EXPIRE:表示到期隔离 MANMADE:表示主动退还隔离 NOTISOLATED:表示未隔离 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "NOTISOLATED"
|
||||
},
|
||||
{
|
||||
"name": "GPUInfo",
|
||||
"type": "json",
|
||||
"desc": "GPU信息。如果是gpu类型子机,该值会返回GPU信息,如果是其他类型子机则不返回。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "LicenseType",
|
||||
"type": "文本",
|
||||
"desc": "实例的操作系统许可类型,默认为TencentCloud",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "DisableApiTermination",
|
||||
"type": "Boolean",
|
||||
"desc": "实例销毁保护标志,表示是否允许通过api接口删除实例。取值范围: TRUE:表示开启实例保护,不允许通过api接口删除实例 FALSE:表示关闭实例保护,允许通过api接口删除实例 默认取值:FALSE。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "DefaultLoginUser",
|
||||
"type": "文本",
|
||||
"desc": "默认登录用户。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "DefaultLoginPort",
|
||||
"type": "整数",
|
||||
"desc": "默认登录端口。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationErrorMsg",
|
||||
"type": "文本",
|
||||
"desc": "实例的最新操作错误信息。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": null
|
||||
}
|
||||
[
|
||||
{
|
||||
"name": "Placement",
|
||||
"type": "Placement",
|
||||
"desc": "实例所在的位置。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "InstanceId",
|
||||
"type": "String",
|
||||
"desc": "实例ID。",
|
||||
"example": "ins-9bxebleo"
|
||||
},
|
||||
{
|
||||
"name": "InstanceType",
|
||||
"type": "String",
|
||||
"desc": "实例机型。",
|
||||
"example": "S1.SMALL1"
|
||||
},
|
||||
{
|
||||
"name": "CPU",
|
||||
"type": "Integer",
|
||||
"desc": "实例的CPU核数,单位:核。",
|
||||
"example": "1"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"type": "Integer",
|
||||
"desc": "实例内存容量,单位:GB。",
|
||||
"example": "1"
|
||||
},
|
||||
{
|
||||
"name": "RestrictState",
|
||||
"type": "String",
|
||||
"desc": "NORMAL:表示正常状态的实例\nEXPIRED:表示过期的实例\nPROTECTIVELY_ISOLATED:表示被安全隔离的实例。",
|
||||
"example": "NORMAL"
|
||||
},
|
||||
{
|
||||
"name": "InstanceName",
|
||||
"type": "String",
|
||||
"desc": "实例名称。",
|
||||
"example": "测试实例"
|
||||
},
|
||||
{
|
||||
"name": "InstanceChargeType",
|
||||
"type": "String",
|
||||
"desc": "PREPAID:表示预付费,即包年包月\nPOSTPAID_BY_HOUR:表示后付费,即按量计费\nCDHPAID:专用宿主机付费,即只对专用宿主机计费,不对专用宿主机上的实例计费。\nSPOTPAID:表示竞价实例付费。",
|
||||
"example": "PREPAID"
|
||||
},
|
||||
{
|
||||
"name": "SystemDisk",
|
||||
"type": "SystemDisk",
|
||||
"desc": "实例系统盘信息。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "DataDisks",
|
||||
"type": "Array of DataDisk",
|
||||
"desc": "实例数据盘信息。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "PrivateIpAddresses",
|
||||
"type": "Array of String",
|
||||
"desc": "实例主网卡的内网IP列表。",
|
||||
"example": "[\"172.16.32.78\"]"
|
||||
},
|
||||
{
|
||||
"name": "PublicIpAddresses",
|
||||
"type": "Array of String",
|
||||
"desc": "实例主网卡的公网IP列表。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "[\"123.207.11.190\"]"
|
||||
},
|
||||
{
|
||||
"name": "InternetAccessible",
|
||||
"type": "InternetAccessible",
|
||||
"desc": "实例带宽信息。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "VirtualPrivateCloud",
|
||||
"type": "VirtualPrivateCloud",
|
||||
"desc": "实例所属虚拟私有网络信息。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "ImageId",
|
||||
"type": "String",
|
||||
"desc": "生产实例所使用的镜像ID。",
|
||||
"example": "img-9qabwvbn"
|
||||
},
|
||||
{
|
||||
"name": "RenewFlag",
|
||||
"type": "String",
|
||||
"desc": "NOTIFY_AND_MANUAL_RENEW:表示通知即将过期,但不自动续费\nNOTIFY_AND_AUTO_RENEW:表示通知即将过期,而且自动续费\nDISABLE_NOTIFY_AND_MANUAL_RENEW:表示不通知即将过期,也不自动续费。\n注意:后付费模式本项为null",
|
||||
"example": "NOTIFY_AND_MANUAL_RENEW"
|
||||
},
|
||||
{
|
||||
"name": "CreatedTime",
|
||||
"type": "Timestamp ISO8601",
|
||||
"desc": "创建时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。",
|
||||
"example": "2020-03-10T02:43:51Z"
|
||||
},
|
||||
{
|
||||
"name": "ExpiredTime",
|
||||
"type": "Timestamp ISO8601",
|
||||
"desc": "到期时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。注意:后付费模式本项为null",
|
||||
"example": "2020-04-10T02:47:36Z"
|
||||
},
|
||||
{
|
||||
"name": "OsName",
|
||||
"type": "String",
|
||||
"desc": "操作系统名称。",
|
||||
"example": "CentOS 7.6 64bit"
|
||||
},
|
||||
{
|
||||
"name": "SecurityGroupIds",
|
||||
"type": "Array of String",
|
||||
"desc": "实例所属安全组。该参数可以通过调用 DescribeSecurityGroups 的返回值中的sgId字段来获取。",
|
||||
"example": "[\"sg-p1ezv4wz\"]"
|
||||
},
|
||||
{
|
||||
"name": "LoginSettings",
|
||||
"type": "LoginSettings",
|
||||
"desc": "实例登录设置。目前只返回实例所关联的密钥。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "InstanceState",
|
||||
"type": "String",
|
||||
"desc": "PENDING:表示创建中\nLAUNCH_FAILED:表示创建失败\nRUNNING:表示运行中\nSTOPPED:表示关机\nSTARTING:表示开机中\nSTOPPING:表示关机中\nREBOOTING:表示重启中\nSHUTDOWN:表示停止待销毁\nTERMINATING:表示销毁中。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"type": "Array of Tag",
|
||||
"desc": "实例关联的标签列表。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "StopChargingMode",
|
||||
"type": "String",
|
||||
"desc": "KEEP_CHARGING:关机继续收费\nSTOP_CHARGING:关机停止收费\nNOT_APPLICABLE:实例处于非关机状态或者不适用关机停止计费的条件",
|
||||
"example": "NOT_APPLICABLE"
|
||||
},
|
||||
{
|
||||
"name": "Uuid",
|
||||
"type": "String",
|
||||
"desc": "实例全局唯一ID",
|
||||
"example": "68b510db-b4c1-4630-a62b-73d0c7c970f9"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperation",
|
||||
"type": "String",
|
||||
"desc": "实例的最新操作。例:StopInstances、ResetInstance。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "RenewInstances"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationState",
|
||||
"type": "String",
|
||||
"desc": "SUCCESS:表示操作成功\nOPERATING:表示操作执行中\nFAILED:表示操作失败注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "SUCCESS"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationRequestId",
|
||||
"type": "String",
|
||||
"desc": "实例最新操作的唯一请求 ID。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "3554eb5b-1cfa-471a-ae76-dc436c9d43e8"
|
||||
},
|
||||
{
|
||||
"name": "DisasterRecoverGroupId",
|
||||
"type": "String",
|
||||
"desc": "分散置放群组ID。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "null"
|
||||
},
|
||||
{
|
||||
"name": "IPv6Addresses",
|
||||
"type": "Array of String",
|
||||
"desc": "实例的IPv6地址。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "null"
|
||||
},
|
||||
{
|
||||
"name": "CamRoleName",
|
||||
"type": "String",
|
||||
"desc": "CAM角色名。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "null"
|
||||
},
|
||||
{
|
||||
"name": "HpcClusterId",
|
||||
"type": "String",
|
||||
"desc": "高性能计算集群ID。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "null"
|
||||
},
|
||||
{
|
||||
"name": "RdmaIpAddresses",
|
||||
"type": "Array of String",
|
||||
"desc": "高性能计算集群IP列表。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "null"
|
||||
},
|
||||
{
|
||||
"name": "DedicatedClusterId",
|
||||
"type": "String",
|
||||
"desc": "实例所在的专用集群ID。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "cluster-du3jken"
|
||||
},
|
||||
{
|
||||
"name": "IsolatedSource",
|
||||
"type": "String",
|
||||
"desc": "ARREAR:表示欠费隔离\nEXPIRE:表示到期隔离\nMANMADE:表示主动退还隔离\nNOTISOLATED:表示未隔离",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "GPUInfo",
|
||||
"type": "GPUInfo",
|
||||
"desc": "GPU信息。如果是gpu类型子机,该值会返回GPU信息,如果是其他类型子机则不返回。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "LicenseType",
|
||||
"type": "String",
|
||||
"desc": "实例的操作系统许可类型,默认为TencentCloud",
|
||||
"example": "TencentCloud"
|
||||
},
|
||||
{
|
||||
"name": "DisableApiTermination",
|
||||
"type": "Boolean",
|
||||
"desc": "TRUE:表示开启实例保护,不允许通过api接口删除实例\nFALSE:表示关闭实例保护,允许通过api接口删除实例默认取值:FALSE。",
|
||||
"example": "false"
|
||||
},
|
||||
{
|
||||
"name": "DefaultLoginUser",
|
||||
"type": "String",
|
||||
"desc": "默认登录用户。",
|
||||
"example": "root"
|
||||
},
|
||||
{
|
||||
"name": "DefaultLoginPort",
|
||||
"type": "Integer",
|
||||
"desc": "默认登录端口。",
|
||||
"example": "22"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationErrorMsg",
|
||||
"type": "String",
|
||||
"desc": "实例的最新操作错误信息。注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "None"
|
||||
}
|
||||
]
|
@@ -182,6 +182,9 @@ class CIManager(object):
|
||||
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
if not ci_type:
|
||||
return res
|
||||
|
||||
res["ci_type"] = ci_type.name
|
||||
|
||||
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
|
||||
@@ -392,8 +395,9 @@ class CIManager(object):
|
||||
k not in ci_type_attrs_alias and _no_attribute_policy == ExistPolicy.REJECT):
|
||||
return abort(400, ErrFormat.attribute_not_found.format(k))
|
||||
|
||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and (
|
||||
ci_type_attrs_alias.get(k) not in limit_attrs):
|
||||
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or
|
||||
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name))
|
||||
if limit_attrs and _attr_name not in limit_attrs:
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
||||
@@ -511,18 +515,20 @@ class CIManager(object):
|
||||
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
|
||||
|
||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
||||
attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
|
||||
for attr_name in attr_names:
|
||||
value_table = TableMap(attr_name=attr_name).table
|
||||
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
|
||||
for attr in attrs:
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
|
||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(
|
||||
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
|
||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(
|
||||
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
|
||||
item.delete(commit=False)
|
||||
|
||||
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
|
||||
@@ -886,12 +892,14 @@ class CIRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ancestor_ids(cls, ci_ids, level=1):
|
||||
for _ in range(level):
|
||||
cis = db.session.query(CIRelation.first_ci_id).filter(
|
||||
level2ids = dict()
|
||||
for _level in range(1, level + 1):
|
||||
cis = db.session.query(CIRelation.first_ci_id, CIRelation.ancestor_ids).filter(
|
||||
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||
ci_ids = [i.first_ci_id for i in cis]
|
||||
level2ids[_level + 1] = {int(i.ancestor_ids.split(',')[-1]) for i in cis if i.ancestor_ids}
|
||||
|
||||
return ci_ids
|
||||
return ci_ids, level2ids
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
@@ -918,13 +926,14 @@ class CIRelationManager(object):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
|
||||
|
||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
|
||||
existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
ancestor_ids=ancestor_ids,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
if existed is not None:
|
||||
@@ -960,11 +969,12 @@ class CIRelationManager(object):
|
||||
|
||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id)
|
||||
relation_type_id=relation_type_id,
|
||||
ancestor_ids=ancestor_ids)
|
||||
|
||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
|
||||
if more is not None:
|
||||
existed.upadte(more=more)
|
||||
@@ -988,53 +998,56 @@ class CIRelationManager(object):
|
||||
his_manager = CIRelationHistoryManager()
|
||||
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||
|
||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
|
||||
return cr_id
|
||||
|
||||
@classmethod
|
||||
def delete_2(cls, first_ci_id, second_ci_id):
|
||||
def delete_2(cls, first_ci_id, second_ci_id, ancestor_ids=None):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
ancestor_ids=ancestor_ids,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
|
||||
return cls.delete(cr.id)
|
||||
return cr and cls.delete(cr.id)
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents, children):
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:param children:
|
||||
:param ancestor_ids:
|
||||
:return:
|
||||
"""
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(parent_id, ci_id)
|
||||
cls.add(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
if isinstance(children, list):
|
||||
for child_id in children:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(ci_id, child_id)
|
||||
cls.add(ci_id, child_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
@classmethod
|
||||
def batch_delete(cls, ci_ids, parents):
|
||||
def batch_delete(cls, ci_ids, parents, ancestor_ids=None):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:param ancestor_ids:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id)
|
||||
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
|
||||
class CITriggerManager(object):
|
||||
|
@@ -5,8 +5,10 @@ import copy
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
from toposort import toposort_flatten
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
@@ -22,6 +24,7 @@ from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
@@ -75,12 +78,13 @@ class CITypeManager(object):
|
||||
def get_ci_types(type_name=None):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources("CIType")])
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
|
||||
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
res = list()
|
||||
for type_dict in ci_types:
|
||||
type_dict["unique_key"] = AttributeCache.get(type_dict["unique_id"]).name
|
||||
attr = AttributeCache.get(type_dict["unique_id"])
|
||||
type_dict["unique_key"] = attr and attr.name
|
||||
if resources is None or type_dict['name'] in resources:
|
||||
res.append(type_dict)
|
||||
|
||||
@@ -113,6 +117,9 @@ class CITypeManager(object):
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}:
|
||||
return abort(403, ErrFormat.no_permission2)
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
||||
@@ -131,7 +138,11 @@ class CITypeManager(object):
|
||||
CITypeCache.clean(ci_type.name)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().add_resource(ci_type.name, ResourceTypeEnum.CI)
|
||||
try:
|
||||
ACLManager().add_resource(ci_type.name, ResourceTypeEnum.CI)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
ACLManager().grant_resource_to_role(ci_type.name,
|
||||
RoleEnum.CMDB_READ_ALL,
|
||||
ResourceTypeEnum.CI,
|
||||
@@ -243,7 +254,6 @@ class CITypeGroupManager(object):
|
||||
else:
|
||||
resources = {i['name']: i['permissions'] for i in resources if PermEnum.READ in i.get("permissions")}
|
||||
|
||||
current_app.logger.info(resources)
|
||||
groups = sorted(CITypeGroup.get_by(), key=lambda x: x['order'] or 0)
|
||||
group_types = set()
|
||||
for group in groups:
|
||||
@@ -283,7 +293,10 @@ class CITypeGroupManager(object):
|
||||
"""
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
if name is not None:
|
||||
if name is not None and name != existed.name:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
existed.update(name=name)
|
||||
|
||||
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||
@@ -576,6 +589,11 @@ class CITypeRelationManager(object):
|
||||
ci_type_dict = CITypeCache.get(type_id).to_dict()
|
||||
ci_type_dict["ctr_id"] = relation_inst.id
|
||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
||||
if attr_filter:
|
||||
ci_type_dict["attributes"] = [attr for attr in (ci_type_dict["attributes"] or [])
|
||||
if attr['name'] in attr_filter]
|
||||
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
ci_type_dict["constraint"] = relation_inst.constraint
|
||||
|
||||
@@ -637,6 +655,16 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
if constraint == ConstraintEnum.Many2Many:
|
||||
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
|
||||
to_dict=False, first=True)
|
||||
if other_c and other_c.child_id != c.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
|
||||
if other_p and other_p.parent_id != p.id:
|
||||
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
|
||||
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id,
|
||||
@@ -686,6 +714,24 @@ class CITypeRelationManager(object):
|
||||
|
||||
cls.delete(ctr.id)
|
||||
|
||||
@staticmethod
|
||||
def get_level2constraint(root_id, level):
|
||||
level = level + 1 if level == 1 else level
|
||||
ci = CI.get_by_id(root_id)
|
||||
if ci is None:
|
||||
return dict()
|
||||
|
||||
root_id = ci.type_id
|
||||
level2constraint = dict()
|
||||
for lv in range(1, int(level) + 1):
|
||||
for i in CITypeRelation.get_by(parent_id=root_id, to_dict=False):
|
||||
if i.constraint == ConstraintEnum.Many2Many:
|
||||
root_id = i.child_id
|
||||
level2constraint[lv] = ConstraintEnum.Many2Many
|
||||
break
|
||||
|
||||
return level2constraint
|
||||
|
||||
|
||||
class CITypeAttributeGroupManager(object):
|
||||
cls = CITypeAttributeGroup
|
||||
@@ -697,7 +743,7 @@ class CITypeAttributeGroupManager(object):
|
||||
grouped = list()
|
||||
|
||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||
id2attr = {i['id']: i for i in attributes}
|
||||
id2attr = {i.get('id'): i for i in attributes}
|
||||
|
||||
for group in groups:
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
||||
@@ -863,97 +909,58 @@ class CITypeAttributeGroupManager(object):
|
||||
|
||||
class CITypeTemplateManager(object):
|
||||
@staticmethod
|
||||
def __import(cls, data):
|
||||
id2obj_dicts = {i['id']: i for i in data}
|
||||
existed = cls.get_by(deleted=None, to_dict=False)
|
||||
id2existed = {i.id: i for i in existed}
|
||||
existed_ids = [i.id for i in existed]
|
||||
existed_no_delete_ids = [i.id for i in existed if not i.deleted]
|
||||
def __import(cls, data, unique_key='name'):
|
||||
id2obj_dicts = {i[unique_key]: i for i in data}
|
||||
existed = cls.get_by(to_dict=False)
|
||||
id2existed = {getattr(i, unique_key): i for i in existed}
|
||||
existed_ids = [getattr(i, unique_key) for i in existed]
|
||||
|
||||
id_map = dict()
|
||||
# add
|
||||
for added_id in set(id2obj_dicts.keys()) - set(existed_ids):
|
||||
_id = id2obj_dicts[added_id].pop('id', None)
|
||||
id2obj_dicts[added_id].pop('created_at', None)
|
||||
id2obj_dicts[added_id].pop('updated_at', None)
|
||||
id2obj_dicts[added_id].pop('uid', None)
|
||||
|
||||
if cls == CIType:
|
||||
CITypeManager.add(**id2obj_dicts[added_id])
|
||||
__id = CITypeManager.add(**id2obj_dicts[added_id])
|
||||
CITypeCache.clean(__id)
|
||||
elif cls == CITypeRelation:
|
||||
CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'),
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
)
|
||||
__id = CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'),
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
)
|
||||
else:
|
||||
cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
if cls == Attribute:
|
||||
AttributeCache.clean(obj)
|
||||
__id = obj.id
|
||||
|
||||
id_map[_id] = __id
|
||||
|
||||
# update
|
||||
for updated_id in set(id2obj_dicts.keys()) & set(existed_ids):
|
||||
_id = id2obj_dicts[updated_id].pop('id', None)
|
||||
|
||||
id2existed[updated_id].update(flush=True, **id2obj_dicts[updated_id])
|
||||
|
||||
id_map[_id] = id2existed[updated_id].id
|
||||
|
||||
if cls == Attribute:
|
||||
AttributeCache.clean(id2existed[updated_id])
|
||||
|
||||
if cls == CIType:
|
||||
deleted = id2existed[updated_id].deleted
|
||||
CITypeManager.update(updated_id, **id2obj_dicts[updated_id])
|
||||
if deleted and current_app.config.get("USE_ACL"):
|
||||
type_name = id2obj_dicts[updated_id]['name']
|
||||
ACLManager().add_resource(type_name, ResourceTypeEnum.CI)
|
||||
ACLManager().grant_resource_to_role(type_name,
|
||||
RoleEnum.CMDB_READ_ALL,
|
||||
ResourceTypeEnum.CI,
|
||||
permissions=[PermEnum.READ])
|
||||
ACLManager().grant_resource_to_role(type_name,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI)
|
||||
CITypeCache.clean(id2existed[updated_id].id)
|
||||
|
||||
else:
|
||||
id2existed[updated_id].update(flush=True, **id2obj_dicts[updated_id])
|
||||
|
||||
# delete
|
||||
for deleted_id in set(existed_no_delete_ids) - set(id2obj_dicts.keys()):
|
||||
if cls == CIType:
|
||||
id2existed[deleted_id].soft_delete(flush=True)
|
||||
|
||||
CITypeCache.clean(deleted_id)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE, deleted_id, change=id2existed[deleted_id].to_dict())
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().del_resource(id2existed[deleted_id].name, ResourceTypeEnum.CI)
|
||||
else:
|
||||
id2existed[deleted_id].soft_delete(flush=True)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
def _import_ci_types(self, ci_types):
|
||||
for i in ci_types:
|
||||
i.pop("unique_key", None)
|
||||
|
||||
self.__import(CIType, ci_types)
|
||||
|
||||
def _import_ci_type_groups(self, ci_type_groups):
|
||||
_ci_type_groups = copy.deepcopy(ci_type_groups)
|
||||
for i in _ci_type_groups:
|
||||
i.pop('ci_types', None)
|
||||
|
||||
self.__import(CITypeGroup, _ci_type_groups)
|
||||
|
||||
# import group type items
|
||||
for group in ci_type_groups:
|
||||
existed = CITypeGroupItem.get_by(group_id=group['id'], to_dict=False)
|
||||
for i in existed:
|
||||
i.soft_delete()
|
||||
|
||||
for order, ci_type in enumerate(group.get('ci_types') or []):
|
||||
payload = dict(group_id=group['id'], type_id=ci_type['id'], order=order)
|
||||
CITypeGroupItem.create(**payload)
|
||||
|
||||
def _import_relation_types(self, relation_types):
|
||||
self.__import(RelationType, relation_types)
|
||||
|
||||
def _import_ci_type_relations(self, ci_type_relations):
|
||||
for i in ci_type_relations:
|
||||
i.pop('parent', None)
|
||||
i.pop('child', None)
|
||||
i.pop('relation_type', None)
|
||||
|
||||
self.__import(CITypeRelation, ci_type_relations)
|
||||
return id_map
|
||||
|
||||
def _import_attributes(self, type2attributes):
|
||||
attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]]
|
||||
@@ -962,122 +969,262 @@ class CITypeTemplateManager(object):
|
||||
i.pop('default_show', None)
|
||||
i.pop('is_required', None)
|
||||
i.pop('order', None)
|
||||
i.pop('choice_web_hook', None)
|
||||
i.pop('choice_other', None)
|
||||
i.pop('order', None)
|
||||
choice_value = i.pop('choice_value', None)
|
||||
if not choice_value:
|
||||
i['is_choice'] = False
|
||||
|
||||
attrs.append((i, choice_value))
|
||||
|
||||
self.__import(Attribute, [i[0] for i in attrs])
|
||||
attr_id_map = self.__import(Attribute, [i[0] for i in copy.deepcopy(attrs)])
|
||||
|
||||
for i, choice_value in attrs:
|
||||
if choice_value:
|
||||
AttributeManager.add_choice_values(i['id'], i['value_type'], choice_value)
|
||||
if choice_value and not i.get('choice_web_hook') and not i.get('choice_other'):
|
||||
AttributeManager.add_choice_values(attr_id_map.get(i['id'], i['id']), i['value_type'], choice_value)
|
||||
|
||||
return attr_id_map
|
||||
|
||||
def _import_ci_types(self, ci_types, attr_id_map):
|
||||
for i in ci_types:
|
||||
i.pop("unique_key", None)
|
||||
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
|
||||
i['uid'] = current_user.uid
|
||||
|
||||
return self.__import(CIType, ci_types)
|
||||
|
||||
def _import_ci_type_groups(self, ci_type_groups, type_id_map):
|
||||
_ci_type_groups = copy.deepcopy(ci_type_groups)
|
||||
for i in _ci_type_groups:
|
||||
i.pop('ci_types', None)
|
||||
|
||||
group_id_map = self.__import(CITypeGroup, _ci_type_groups)
|
||||
|
||||
# import group type items
|
||||
for group in ci_type_groups:
|
||||
for order, ci_type in enumerate(group.get('ci_types') or []):
|
||||
payload = dict(group_id=group_id_map.get(group['id'], group['id']),
|
||||
type_id=type_id_map.get(ci_type['id'], ci_type['id']),
|
||||
order=order)
|
||||
existed = CITypeGroupItem.get_by(group_id=payload['group_id'], type_id=payload['type_id'],
|
||||
first=True, to_dict=False)
|
||||
if existed is None:
|
||||
CITypeGroupItem.create(flush=True, **payload)
|
||||
else:
|
||||
existed.update(flush=True, **payload)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
def _import_relation_types(self, relation_types):
|
||||
return self.__import(RelationType, relation_types)
|
||||
|
||||
@staticmethod
|
||||
def _import_type_attributes(type2attributes):
|
||||
# add type attribute
|
||||
def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map):
|
||||
for i in ci_type_relations:
|
||||
i.pop('parent', None)
|
||||
i.pop('child', None)
|
||||
i.pop('relation_type', None)
|
||||
|
||||
i['parent_id'] = type_id_map.get(i['parent_id'], i['parent_id'])
|
||||
i['child_id'] = type_id_map.get(i['child_id'], i['child_id'])
|
||||
i['relation_type_id'] = relation_type_id_map.get(i['relation_type_id'], i['relation_type_id'])
|
||||
|
||||
try:
|
||||
CITypeRelationManager.add(i.get('parent_id'),
|
||||
i.get('child_id'),
|
||||
i.get('relation_type_id'),
|
||||
i.get('constraint'),
|
||||
)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _import_type_attributes(type2attributes, type_id_map, attr_id_map):
|
||||
for type_id in type2attributes:
|
||||
CITypeAttributesCache.clean(type_id_map.get(int(type_id), type_id))
|
||||
|
||||
for type_id in type2attributes:
|
||||
existed = CITypeAttribute.get_by(type_id=type_id, to_dict=False)
|
||||
existed_attr_ids = {i.attr_id: i for i in existed}
|
||||
new_attr_ids = {i['id']: i for i in type2attributes[type_id]}
|
||||
existed = CITypeAttributesCache.get2(type_id_map.get(int(type_id), type_id))
|
||||
existed_attr_names = {attr.name: ta for ta, attr in existed}
|
||||
|
||||
handled = set()
|
||||
for attr in type2attributes[type_id]:
|
||||
payload = dict(type_id=type_id,
|
||||
attr_id=attr['id'],
|
||||
payload = dict(type_id=type_id_map.get(int(type_id), type_id),
|
||||
attr_id=attr_id_map.get(attr['id'], attr['id']),
|
||||
default_show=attr['default_show'],
|
||||
is_required=attr['is_required'],
|
||||
order=attr['order'])
|
||||
if attr['id'] not in existed_attr_ids: # new
|
||||
CITypeAttribute.create(flush=True, **payload)
|
||||
else: # update
|
||||
existed_attr_ids[attr['id']].update(**payload)
|
||||
if attr['name'] not in handled:
|
||||
if attr['name'] not in existed_attr_names: # new
|
||||
CITypeAttribute.create(flush=True, **payload)
|
||||
else: # update
|
||||
existed_attr_names[attr['name']].update(flush=True, **payload)
|
||||
|
||||
# delete
|
||||
for i in existed:
|
||||
if i.attr_id not in new_attr_ids:
|
||||
i.soft_delete()
|
||||
handled.add(attr['name'])
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
for type_id in type2attributes:
|
||||
CITypeAttributesCache.clean(type_id_map.get(int(type_id), type_id))
|
||||
|
||||
@staticmethod
|
||||
def _import_attribute_group(type2attribute_group):
|
||||
def _import_attribute_group(type2attribute_group, type_id_map, attr_id_map):
|
||||
for type_id in type2attribute_group:
|
||||
existed = CITypeAttributeGroup.get_by(type_id=type_id, to_dict=False)
|
||||
for i in existed:
|
||||
i.soft_delete()
|
||||
|
||||
for group in type2attribute_group[type_id] or []:
|
||||
_group = copy.deepcopy(group)
|
||||
_group.pop('attributes', None)
|
||||
_group.pop('id', None)
|
||||
new = CITypeAttributeGroup.create(**_group)
|
||||
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
||||
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
||||
first=True, to_dict=False)
|
||||
if existed is None:
|
||||
_group['type_id'] = type_id_map.get(_group['type_id'], _group['type_id'])
|
||||
|
||||
existed = CITypeAttributeGroupItem.get_by(group_id=new.id, to_dict=False)
|
||||
for i in existed:
|
||||
i.soft_delete()
|
||||
existed = CITypeAttributeGroup.create(flush=True, **_group)
|
||||
|
||||
for order, attr in enumerate(group['attributes'] or []):
|
||||
CITypeAttributeGroupItem.create(group_id=new.id, attr_id=attr['id'], order=order)
|
||||
item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id,
|
||||
attr_id=attr_id_map.get(attr['id'], attr['id']),
|
||||
first=True, to_dict=False)
|
||||
if item_existed is None:
|
||||
CITypeAttributeGroupItem.create(group_id=existed.id,
|
||||
attr_id=attr_id_map.get(attr['id'], attr['id']),
|
||||
order=order)
|
||||
else:
|
||||
item_existed.update(flush=True, order=order)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise Exception(str(e))
|
||||
|
||||
@staticmethod
|
||||
def _import_auto_discovery_rules(rules):
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
|
||||
|
||||
for rule in rules:
|
||||
ci_type = CITypeCache.get(rule.pop('type_name', None))
|
||||
adr = rule.pop('adr', {}) or {}
|
||||
|
||||
if ci_type:
|
||||
rule['type_id'] = ci_type.id
|
||||
if rule.get('adr_name'):
|
||||
ad_rule = AutoDiscoveryRuleCRUD.get_by_name(rule.pop("adr_name"))
|
||||
adr.pop('created_at', None)
|
||||
adr.pop('updated_at', None)
|
||||
adr.pop('id', None)
|
||||
|
||||
if ad_rule:
|
||||
rule['adr_id'] = ad_rule.id
|
||||
ad_rule.update(**adr)
|
||||
|
||||
elif adr:
|
||||
ad_rule = AutoDiscoveryRuleCRUD().add(**adr)
|
||||
rule['adr_id'] = ad_rule.id
|
||||
else:
|
||||
continue
|
||||
|
||||
rule.pop("id", None)
|
||||
rule.pop("created_at", None)
|
||||
rule.pop("updated_at", None)
|
||||
|
||||
rule['uid'] = current_user.uid
|
||||
try:
|
||||
AutoDiscoveryCITypeCRUD.add(**rule)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
|
||||
|
||||
existed = False
|
||||
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
|
||||
if ((i.extra_option or {}).get('alias') or None) == (
|
||||
(rule.get('extra_option') or {}).get('alias') or None):
|
||||
existed = True
|
||||
AutoDiscoveryCITypeCRUD().update(i.id, **rule)
|
||||
break
|
||||
|
||||
if not existed:
|
||||
try:
|
||||
AutoDiscoveryCITypeCRUD().add(**rule)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
|
||||
|
||||
@staticmethod
|
||||
def _import_icons(icons):
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
for icon_name in icons:
|
||||
if icons[icon_name]:
|
||||
try:
|
||||
CommonFileCRUD().save_str_to_file(icon_name, icons[icon_name])
|
||||
except Exception as e:
|
||||
current_app.logger.warning("save icon failed: {}".format(e))
|
||||
|
||||
def import_template(self, tpt):
|
||||
import time
|
||||
s = time.time()
|
||||
self._import_attributes(tpt.get('type2attributes') or {})
|
||||
attr_id_map = self._import_attributes(tpt.get('type2attributes') or {})
|
||||
current_app.logger.info('import attributes cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_ci_types(tpt.get('ci_types') or [])
|
||||
ci_type_id_map = self._import_ci_types(tpt.get('ci_types') or [], attr_id_map)
|
||||
current_app.logger.info('import ci_types cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_ci_type_groups(tpt.get('ci_type_groups') or [])
|
||||
self._import_ci_type_groups(tpt.get('ci_type_groups') or [], ci_type_id_map)
|
||||
current_app.logger.info('import ci_type_groups cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_relation_types(tpt.get('relation_types') or [])
|
||||
relation_type_id_map = self._import_relation_types(tpt.get('relation_types') or [])
|
||||
current_app.logger.info('import relation_types cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_ci_type_relations(tpt.get('ci_type_relations') or [])
|
||||
self._import_ci_type_relations(tpt.get('ci_type_relations') or [], ci_type_id_map, relation_type_id_map)
|
||||
current_app.logger.info('import ci_type_relations cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_type_attributes(tpt.get('type2attributes') or {})
|
||||
self._import_type_attributes(tpt.get('type2attributes') or {}, ci_type_id_map, attr_id_map)
|
||||
current_app.logger.info('import type2attributes cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_attribute_group(tpt.get('type2attribute_group') or {})
|
||||
self._import_attribute_group(tpt.get('type2attribute_group') or {}, ci_type_id_map, attr_id_map)
|
||||
current_app.logger.info('import type2attribute_group cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_auto_discovery_rules(tpt.get('ci_type_auto_discovery_rules') or [])
|
||||
current_app.logger.info('import ci_type_auto_discovery_rules cost: {}'.format(time.time() - s))
|
||||
|
||||
s = time.time()
|
||||
self._import_icons(tpt.get('icons') or {})
|
||||
current_app.logger.info('import icons cost: {}'.format(time.time() - s))
|
||||
|
||||
@staticmethod
|
||||
def export_template():
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(),
|
||||
ci_type_groups=CITypeGroupManager.get(),
|
||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||
ci_type_relations=CITypeRelationManager.get(),
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
icons=dict()
|
||||
)
|
||||
|
||||
def get_icon_value(icon):
|
||||
try:
|
||||
return CommonFileCRUD().get_file_binary_str(icon)
|
||||
except:
|
||||
return ""
|
||||
|
||||
ad_rules = AutoDiscoveryCITypeCRUD.get_all()
|
||||
rules = []
|
||||
@@ -1088,23 +1235,91 @@ class CITypeTemplateManager(object):
|
||||
if r.get('adr_id'):
|
||||
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
|
||||
r['adr_name'] = adr and adr.name
|
||||
r['adr'] = adr and adr.to_dict() or {}
|
||||
|
||||
icon_url = r['adr'].get('option', {}).get('icon', {}).get('url')
|
||||
if icon_url and icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
rules.append(r)
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(),
|
||||
ci_type_groups=CITypeGroupManager.get(),
|
||||
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
|
||||
ci_type_relations=CITypeRelationManager.get(),
|
||||
ci_type_auto_discovery_rules=rules,
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict()
|
||||
)
|
||||
tpt['ci_type_auto_discovery_rules'] = rules
|
||||
|
||||
for ci_type in tpt['ci_types']:
|
||||
if ci_type['icon'] and len(ci_type['icon'].split('$$')) > 3:
|
||||
icon_url = ci_type['icon'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
|
||||
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
|
||||
|
||||
for attr in tpt['type2attributes'][ci_type['id']]:
|
||||
for i in (attr.get('choice_value') or []):
|
||||
if (i[1] or {}).get('icon', {}).get('url') and len(i[1]['icon']['url'].split('$$')) > 3:
|
||||
icon_url = i[1]['icon']['url'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
|
||||
|
||||
return tpt
|
||||
|
||||
@staticmethod
|
||||
def export_template_by_type(type_id):
|
||||
ci_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found2.format("id={}".format(type_id)))
|
||||
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
|
||||
from api.lib.common_setting.upload_file import CommonFileCRUD
|
||||
|
||||
tpt = dict(
|
||||
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name),
|
||||
ci_type_auto_discovery_rules=list(),
|
||||
type2attributes=dict(),
|
||||
type2attribute_group=dict(),
|
||||
icons=dict()
|
||||
)
|
||||
|
||||
def get_icon_value(icon):
|
||||
try:
|
||||
return CommonFileCRUD().get_file_binary_str(icon)
|
||||
except:
|
||||
return ""
|
||||
|
||||
ad_rules = AutoDiscoveryCITypeCRUD.get_by_type_id(ci_type.id)
|
||||
rules = []
|
||||
for r in ad_rules:
|
||||
r = r.to_dict()
|
||||
r['type_name'] = ci_type and ci_type.name
|
||||
if r.get('adr_id'):
|
||||
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
|
||||
r['adr_name'] = adr and adr.name
|
||||
r['adr'] = adr and adr.to_dict() or {}
|
||||
|
||||
icon_url = r['adr'].get('option', {}).get('icon', {}).get('url')
|
||||
if icon_url and icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
rules.append(r)
|
||||
tpt['ci_type_auto_discovery_rules'] = rules
|
||||
|
||||
for ci_type in tpt['ci_types']:
|
||||
if ci_type['icon'] and len(ci_type['icon'].split('$$')) > 3:
|
||||
icon_url = ci_type['icon'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
|
||||
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
|
||||
|
||||
for attr in tpt['type2attributes'][ci_type['id']]:
|
||||
for i in (attr.get('choice_value') or []):
|
||||
if (i[1] or {}).get('icon', {}).get('url') and len(i[1]['icon']['url'].split('$$')) > 3:
|
||||
icon_url = i[1]['icon']['url'].split('$$')[3]
|
||||
if icon_url not in tpt['icons']:
|
||||
tpt['icons'][icon_url] = get_icon_value(icon_url)
|
||||
|
||||
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
|
||||
|
||||
return tpt
|
||||
|
@@ -69,6 +69,7 @@ class ResourceTypeEnum(BaseEnum):
|
||||
CI_TYPE_RELATION = "CITypeRelation" # create/delete/grant
|
||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||
CI_FILTER = "CIFilter" # read
|
||||
PAGE = "page" # read
|
||||
|
||||
|
||||
class PermEnum(BaseEnum):
|
||||
@@ -100,6 +101,7 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
|
||||
|
||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
|
||||
|
||||
|
@@ -135,7 +135,7 @@ class AttributeHistoryManger(object):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
cis = CIManager().get_cis_by_ids(list(ci_ids),
|
||||
unique_required=True)
|
||||
cis = {i['_id']: i for i in cis}
|
||||
cis = {i['_id']: i for i in cis if i}
|
||||
|
||||
return total, res, cis
|
||||
|
||||
|
@@ -143,11 +143,14 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
first=True, to_dict=False)
|
||||
|
||||
if obj is not None:
|
||||
resource = None
|
||||
if current_app.config.get('USE_ACL'):
|
||||
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
|
||||
obj.soft_delete()
|
||||
|
||||
return resource
|
||||
|
||||
|
||||
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
||||
def decorator_has_perm(func):
|
||||
|
@@ -14,7 +14,10 @@ from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
@@ -229,14 +232,28 @@ class PreferenceManager(object):
|
||||
if not parents:
|
||||
return
|
||||
|
||||
for l in leaf:
|
||||
_find_parent(l)
|
||||
for _l in leaf:
|
||||
_find_parent(_l)
|
||||
|
||||
for node_id in node2show_types:
|
||||
node2show_types[node_id] = [CITypeCache.get(i).to_dict() for i in set(node2show_types[node_id])]
|
||||
|
||||
topo_flatten = list(toposort.toposort_flatten(topo))
|
||||
level2constraint = {}
|
||||
for i, _ in enumerate(topo_flatten[1:]):
|
||||
ctr = CITypeRelation.get_by(
|
||||
parent_id=topo_flatten[i], child_id=topo_flatten[i + 1], first=True, to_dict=False)
|
||||
level2constraint[i + 1] = ctr and ctr.constraint
|
||||
|
||||
if leaf2show_types.get(topo_flatten[-1]):
|
||||
ctr = CITypeRelation.get_by(
|
||||
parent_id=topo_flatten[-1],
|
||||
child_id=leaf2show_types[topo_flatten[-1]][0], first=True, to_dict=False)
|
||||
level2constraint[len(topo_flatten)] = ctr and ctr.constraint
|
||||
|
||||
result[view_name] = dict(topo=list(map(list, toposort.toposort(topo))),
|
||||
topo_flatten=list(toposort.toposort_flatten(topo)),
|
||||
topo_flatten=topo_flatten,
|
||||
level2constraint=level2constraint,
|
||||
leaf=leaf,
|
||||
leaf2show_types=leaf2show_types,
|
||||
node2show_types=node2show_types,
|
||||
@@ -338,3 +355,29 @@ class PreferenceManager(object):
|
||||
|
||||
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def can_edit_relation(parent_id, child_id):
|
||||
views = PreferenceRelationView.get_by(to_dict=False)
|
||||
for view in views:
|
||||
has_m2m = False
|
||||
last_node_id = None
|
||||
for cr in view.cr_ids:
|
||||
_rel = CITypeRelation.get_by(parent_id=cr['parent_id'], child_id=cr['child_id'],
|
||||
first=True, to_dict=False)
|
||||
if _rel and _rel.constraint == ConstraintEnum.Many2Many:
|
||||
has_m2m = True
|
||||
|
||||
if parent_id == _rel.parent_id and child_id == _rel.child_id:
|
||||
return False
|
||||
|
||||
if _rel:
|
||||
last_node_id = _rel.child_id
|
||||
|
||||
if parent_id == last_node_id:
|
||||
rels = CITypeRelation.get_by(parent_id=last_node_id, to_dict=False)
|
||||
for rel in rels:
|
||||
if rel.child_id == child_id and has_m2m:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@@ -1,100 +1,138 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from api.lib.resp_format import CommonErrFormat
|
||||
|
||||
|
||||
class ErrFormat(CommonErrFormat):
|
||||
invalid_relation_type = "无效的关系类型: {}"
|
||||
ci_type_not_found = "模型不存在!"
|
||||
argument_attributes_must_be_list = "参数 attributes 类型必须是列表"
|
||||
argument_file_not_found = "文件似乎并未上传"
|
||||
ci_type_config = _l("CI Model") # 模型配置
|
||||
|
||||
attribute_not_found = "属性 {} 不存在!"
|
||||
attribute_is_unique_id = "该属性是模型的唯一标识,不能被删除!"
|
||||
attribute_is_ref_by_type = "该属性被模型 {} 引用, 不能删除!"
|
||||
attribute_value_type_cannot_change = "属性的值类型不允许修改!"
|
||||
attribute_list_value_cannot_change = "多值不被允许修改!"
|
||||
attribute_index_cannot_change = "修改索引 非管理员不被允许!"
|
||||
attribute_index_change_failed = "索引切换失败!"
|
||||
invalid_choice_values = "预定义值的类型不对!"
|
||||
attribute_name_duplicate = "重复的属性名 {}"
|
||||
add_attribute_failed = "创建属性 {} 失败!"
|
||||
update_attribute_failed = "修改属性 {} 失败!"
|
||||
cannot_edit_attribute = "您没有权限修改该属性!"
|
||||
cannot_delete_attribute = "目前只允许 属性创建人、管理员 删除属性!"
|
||||
attribute_name_cannot_be_builtin = "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type"
|
||||
attribute_choice_other_invalid = "预定义值: 其他模型请求参数不合法!"
|
||||
invalid_relation_type = _l("Invalid relation type: {}") # 无效的关系类型: {}
|
||||
ci_type_not_found = _l("CIType is not found") # 模型不存在!
|
||||
|
||||
ci_not_found = "CI {} 不存在"
|
||||
unique_constraint = "多属性联合唯一校验不通过: {}"
|
||||
unique_value_not_found = "模型的主键 {} 不存在!"
|
||||
unique_key_required = "主键字段 {} 缺失"
|
||||
ci_is_already_existed = "CI 已经存在!"
|
||||
relation_constraint = "关系约束: {}, 校验失败 "
|
||||
relation_not_found = "CI关系: {} 不存在"
|
||||
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"
|
||||
# 参数 attributes 类型必须是列表
|
||||
argument_attributes_must_be_list = _l("The type of parameter attributes must be a list")
|
||||
argument_file_not_found = _l("The file doesn't seem to be uploaded") # 文件似乎并未上传
|
||||
|
||||
ci_type_not_found2 = "模型 {} 不存在"
|
||||
ci_type_is_already_existed = "模型 {} 已经存在"
|
||||
unique_key_not_define = "主键未定义或者已被删除"
|
||||
only_owner_can_delete = "只有创建人才能删除它!"
|
||||
ci_exists_and_cannot_delete_type = "因为CI已经存在,不能删除模型"
|
||||
ci_relation_view_exists_and_cannot_delete_type = "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
ci_type_group_not_found = "模型分组 {} 不存在"
|
||||
ci_type_group_exists = "模型分组 {} 已经存在"
|
||||
ci_type_relation_not_found = "模型关系 {} 不存在"
|
||||
ci_type_attribute_group_duplicate = "属性分组 {} 已存在"
|
||||
ci_type_attribute_group_not_found = "属性分组 {} 不存在"
|
||||
ci_type_group_attribute_not_found = "属性组<{0}> - 属性<{1}> 不存在"
|
||||
unique_constraint_duplicate = "唯一约束已经存在!"
|
||||
unique_constraint_invalid = "唯一约束的属性不能是 JSON 和 多值"
|
||||
ci_type_trigger_duplicate = "重复的触发器"
|
||||
ci_type_trigger_not_found = "触发器 {} 不存在"
|
||||
attribute_not_found = _l("Attribute {} does not exist!") # 属性 {} 不存在!
|
||||
attribute_is_unique_id = _l(
|
||||
"This attribute is the unique identifier of the model and cannot be deleted!") # 该属性是模型的唯一标识,不能被删除!
|
||||
attribute_is_ref_by_type = _l(
|
||||
"This attribute is referenced by model {} and cannot be deleted!") # 该属性被模型 {} 引用, 不能删除!
|
||||
attribute_value_type_cannot_change = _l(
|
||||
"The value type of the attribute is not allowed to be modified!") # 属性的值类型不允许修改!
|
||||
attribute_list_value_cannot_change = _l("Multiple values are not allowed to be modified!") # 多值不被允许修改!
|
||||
# 修改索引 非管理员不被允许!
|
||||
attribute_index_cannot_change = _l("Modifying the index is not allowed for non-administrators!")
|
||||
attribute_index_change_failed = _l("Index switching failed!") # 索引切换失败!
|
||||
invalid_choice_values = _l("The predefined value is of the wrong type!") # 预定义值的类型不对!
|
||||
attribute_name_duplicate = _l("Duplicate attribute name {}") # 重复的属性名 {}
|
||||
add_attribute_failed = _l("Failed to create attribute {}!") # 创建属性 {} 失败!
|
||||
update_attribute_failed = _l("Modify attribute {} failed!") # 修改属性 {} 失败!
|
||||
cannot_edit_attribute = _l("You do not have permission to modify this attribute!") # 您没有权限修改该属性!
|
||||
cannot_delete_attribute = _l(
|
||||
"Only creators and administrators are allowed to delete attributes!") # 目前只允许 属性创建人、管理员 删除属性!
|
||||
# 属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type
|
||||
attribute_name_cannot_be_builtin = _l(
|
||||
"Attribute field names cannot be built-in fields: id, _id, ci_id, type, _type, ci_type")
|
||||
attribute_choice_other_invalid = _l(
|
||||
"Predefined value: Other model request parameters are illegal!") # 预定义值: 其他模型请求参数不合法!
|
||||
|
||||
record_not_found = "操作记录 {} 不存在"
|
||||
cannot_delete_unique = "不能删除唯一标识"
|
||||
cannot_delete_default_order_attr = "不能删除默认排序的属性"
|
||||
ci_not_found = _l("CI {} does not exist") # CI {} 不存在
|
||||
unique_constraint = _l("Multiple attribute joint unique verification failed: {}") # 多属性联合唯一校验不通过: {}
|
||||
unique_value_not_found = _l("The model's primary key {} does not exist!") # 模型的主键 {} 不存在!
|
||||
unique_key_required = _l("Primary key {} is missing") # 主键字段 {} 缺失
|
||||
ci_is_already_existed = _l("CI already exists!") # CI 已经存在!
|
||||
relation_constraint = _l("Relationship constraint: {}, verification failed") # 关系约束: {}, 校验失败
|
||||
# 多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!
|
||||
m2m_relation_constraint = _l(
|
||||
"Many-to-many relationship constraint: Model {} <-> {} already has a many-to-many relationship!")
|
||||
|
||||
preference_relation_view_node_required = "没有选择节点"
|
||||
preference_search_option_not_found = "该搜索选项不存在!"
|
||||
preference_search_option_exists = "该搜索选项命名重复!"
|
||||
relation_not_found = _l("CI relationship: {} does not exist") # CI关系: {} 不存在
|
||||
|
||||
relation_type_exists = "关系类型 {} 已经存在"
|
||||
relation_type_not_found = "关系类型 {} 不存在"
|
||||
# 搜索表达式里小括号前不支持: 或、非
|
||||
ci_search_Parentheses_invalid = _l("In search expressions, not supported before parentheses: or, not")
|
||||
|
||||
attribute_value_invalid = "无效的属性值: {}"
|
||||
attribute_value_invalid2 = "{} 无效的值: {}"
|
||||
not_in_choice_values = "{} 不在预定义值里"
|
||||
attribute_value_unique_required = "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
attribute_value_required = "属性 {} 值必须存在"
|
||||
attribute_value_unknown_error = "新增或者修改属性值未知错误: {}"
|
||||
ci_type_not_found2 = _l("Model {} does not exist") # 模型 {} 不存在
|
||||
ci_type_is_already_existed = _l("Model {} already exists") # 模型 {} 已经存在
|
||||
unique_key_not_define = _l("The primary key is undefined or has been deleted") # 主键未定义或者已被删除
|
||||
only_owner_can_delete = _l("Only the creator can delete it!") # 只有创建人才能删除它!
|
||||
ci_exists_and_cannot_delete_type = _l(
|
||||
"The model cannot be deleted because the CI already exists") # 因为CI已经存在,不能删除模型
|
||||
|
||||
custom_name_duplicate = "订制名重复"
|
||||
# 因为关系视图 {} 引用了该模型,不能删除模型
|
||||
ci_relation_view_exists_and_cannot_delete_type = _l(
|
||||
"The model cannot be deleted because the model is referenced by the relational view {}")
|
||||
ci_type_group_not_found = _l("Model group {} does not exist") # 模型分组 {} 不存在
|
||||
ci_type_group_exists = _l("Model group {} already exists") # 模型分组 {} 已经存在
|
||||
ci_type_relation_not_found = _l("Model relationship {} does not exist") # 模型关系 {} 不存在
|
||||
ci_type_attribute_group_duplicate = _l("Attribute group {} already exists") # 属性分组 {} 已存在
|
||||
ci_type_attribute_group_not_found = _l("Attribute group {} does not exist") # 属性分组 {} 不存在
|
||||
# 属性组<{0}> - 属性<{1}> 不存在
|
||||
ci_type_group_attribute_not_found = _l("Attribute group <{0}> - attribute <{1}> does not exist")
|
||||
unique_constraint_duplicate = _l("The unique constraint already exists!") # 唯一约束已经存在!
|
||||
# 唯一约束的属性不能是 JSON 和 多值
|
||||
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
||||
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
||||
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
|
||||
|
||||
limit_ci_type = "模型数超过限制: {}"
|
||||
limit_ci = "CI数超过限制: {}"
|
||||
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
|
||||
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
||||
cannot_delete_default_order_attr = _l("Cannot delete default sorted attributes") # 不能删除默认排序的属性
|
||||
|
||||
adr_duplicate = "自动发现规则: {} 已经存在!"
|
||||
adr_not_found = "自动发现规则: {} 不存在!"
|
||||
adr_referenced = "该自动发现规则被模型引用, 不能删除!"
|
||||
ad_duplicate = "自动发现规则的应用不能重复定义!"
|
||||
ad_not_found = "您要修改的自动发现: {} 不存在!"
|
||||
ad_not_unique_key = "属性字段没有包括唯一标识: {}"
|
||||
adc_not_found = "自动发现的实例不存在!"
|
||||
adt_not_found = "模型并未关联该自动发现!"
|
||||
adt_secret_no_permission = "只有创建人才能修改Secret!"
|
||||
cannot_delete_adt = "该规则已经有自动发现的实例, 不能被删除!"
|
||||
adr_default_ref_once = "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
adr_unique_key_required = "unique_key方法必须返回非空字符串!"
|
||||
adr_plugin_attributes_list_required = "attributes方法必须返回的是list"
|
||||
adr_plugin_attributes_list_no_empty = "attributes方法返回的list不能为空!"
|
||||
adt_target_all_no_permission = "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
adt_target_expr_no_permission = "执行机器权限检查不通过: {}"
|
||||
preference_relation_view_node_required = _l("No node selected") # 没有选择节点
|
||||
preference_search_option_not_found = _l("This search option does not exist!") # 该搜索选项不存在!
|
||||
preference_search_option_exists = _l("This search option has a duplicate name!") # 该搜索选项命名重复!
|
||||
|
||||
ci_filter_name_cannot_be_empty = "CI过滤授权 必须命名!"
|
||||
ci_filter_perm_cannot_or_query = "CI过滤授权 暂时不支持 或 查询"
|
||||
ci_filter_perm_attr_no_permission = "您没有属性 {} 的操作权限!"
|
||||
ci_filter_perm_ci_no_permission = "您没有该CI的操作权限!"
|
||||
relation_type_exists = _l("Relationship type {} already exists") # 关系类型 {} 已经存在
|
||||
relation_type_not_found = _l("Relationship type {} does not exist") # 关系类型 {} 不存在
|
||||
|
||||
password_save_failed = "保存密码失败: {}"
|
||||
password_load_failed = "获取密码失败: {}"
|
||||
attribute_value_invalid = _l("Invalid attribute value: {}") # 无效的属性值: {}
|
||||
attribute_value_invalid2 = _l("{} Invalid value: {}") # {} 无效的值: {}
|
||||
not_in_choice_values = _l("{} is not in the predefined values") # {} 不在预定义值里
|
||||
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在
|
||||
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
|
||||
attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在
|
||||
|
||||
# 新增或者修改属性值未知错误: {}
|
||||
attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}")
|
||||
|
||||
custom_name_duplicate = _l("Duplicate custom name") # 订制名重复
|
||||
|
||||
limit_ci_type = _l("Number of models exceeds limit: {}") # 模型数超过限制: {}
|
||||
limit_ci = _l("The number of CIs exceeds the limit: {}") # CI数超过限制: {}
|
||||
|
||||
adr_duplicate = _l("Auto-discovery rule: {} already exists!") # 自动发现规则: {} 已经存在!
|
||||
adr_not_found = _l("Auto-discovery rule: {} does not exist!") # 自动发现规则: {} 不存在!
|
||||
# 该自动发现规则被模型引用, 不能删除!
|
||||
adr_referenced = _l("This auto-discovery rule is referenced by the model and cannot be deleted!")
|
||||
# 自动发现规则的应用不能重复定义!
|
||||
ad_duplicate = _l("The application of auto-discovery rules cannot be defined repeatedly!")
|
||||
ad_not_found = _l("The auto-discovery you want to modify: {} does not exist!") # 您要修改的自动发现: {} 不存在!
|
||||
ad_not_unique_key = _l("Attribute does not include unique identifier: {}") # 属性字段没有包括唯一标识: {}
|
||||
adc_not_found = _l("The auto-discovery instance does not exist!") # 自动发现的实例不存在!
|
||||
adt_not_found = _l("The model is not associated with this auto-discovery!") # 模型并未关联该自动发现!
|
||||
adt_secret_no_permission = _l("Only the creator can modify the Secret!") # 只有创建人才能修改Secret!
|
||||
# 该规则已经有自动发现的实例, 不能被删除!
|
||||
cannot_delete_adt = _l("This rule already has auto-discovery instances and cannot be deleted!")
|
||||
# 该默认的自动发现规则 已经被模型 {} 引用!
|
||||
adr_default_ref_once = _l("The default auto-discovery rule is already referenced by model {}!")
|
||||
# unique_key方法必须返回非空字符串!
|
||||
adr_unique_key_required = _l("The unique_key method must return a non-empty string!")
|
||||
adr_plugin_attributes_list_required = _l("The attributes method must return a list") # attributes方法必须返回的是list
|
||||
# attributes方法返回的list不能为空!
|
||||
adr_plugin_attributes_list_no_empty = _l("The list returned by the attributes method cannot be empty!")
|
||||
# 只有管理员才可以定义执行机器为: 所有节点!
|
||||
adt_target_all_no_permission = _l("Only administrators can define execution targets as: all nodes!")
|
||||
adt_target_expr_no_permission = _l("Execute targets permission check failed: {}") # 执行机器权限检查不通过: {}
|
||||
|
||||
ci_filter_name_cannot_be_empty = _l("CI filter authorization must be named!") # CI过滤授权 必须命名!
|
||||
ci_filter_perm_cannot_or_query = _l(
|
||||
"CI filter authorization is currently not supported or query") # CI过滤授权 暂时不支持 或 查询
|
||||
# 您没有属性 {} 的操作权限!
|
||||
ci_filter_perm_attr_no_permission = _l("You do not have permission to operate attribute {}!")
|
||||
ci_filter_perm_ci_no_permission = _l("You do not have permission to operate this CI!") # 您没有该CI的操作权限!
|
||||
|
||||
password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {}
|
||||
password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {}
|
||||
|
@@ -1,6 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import json
|
||||
from collections import Counter
|
||||
|
||||
@@ -10,11 +8,14 @@ from flask import current_app
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
|
||||
|
||||
class Search(object):
|
||||
@@ -26,7 +27,9 @@ class Search(object):
|
||||
page=1,
|
||||
count=None,
|
||||
sort=None,
|
||||
reverse=False):
|
||||
reverse=False,
|
||||
ancestor_ids=None,
|
||||
has_m2m=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
@@ -38,25 +41,82 @@ class Search(object):
|
||||
self.level = level or 0
|
||||
self.reverse = reverse
|
||||
|
||||
def _get_ids(self):
|
||||
self.level2constraint = CITypeRelationManager.get_level2constraint(
|
||||
root_id[0] if root_id and isinstance(root_id, list) else root_id,
|
||||
level[0] if isinstance(level, list) and level else level)
|
||||
|
||||
self.ancestor_ids = ancestor_ids
|
||||
self.has_m2m = has_m2m or False
|
||||
if not self.has_m2m:
|
||||
if self.ancestor_ids:
|
||||
self.has_m2m = True
|
||||
else:
|
||||
level = level[0] if isinstance(level, list) and level else level
|
||||
for _l, c in self.level2constraint.items():
|
||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||
self.has_m2m = True
|
||||
|
||||
def _get_ids(self, ids):
|
||||
if self.level[-1] == 1 and len(ids) == 1:
|
||||
if self.ancestor_ids is None:
|
||||
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
|
||||
|
||||
else:
|
||||
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
|
||||
ancestor_ids=self.ancestor_ids,
|
||||
to_dict=False)}
|
||||
|
||||
return list(seconds)
|
||||
|
||||
merge_ids = []
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
key = []
|
||||
_tmp = []
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
_tmp = list(map(lambda x: list(json.loads(x).keys()),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
if not self.has_m2m:
|
||||
_tmp = map(lambda x: json.loads(x).keys(),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
|
||||
else:
|
||||
if not self.ancestor_ids:
|
||||
if level == 1:
|
||||
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
if level == 1:
|
||||
key, prefix = ["{},{}".format(self.ancestor_ids, i) for i in ids], REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
|
||||
if not key:
|
||||
return []
|
||||
|
||||
if level in self.level:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
return merge_ids
|
||||
|
||||
def _get_reverse_ids(self):
|
||||
def _get_reverse_ids(self, ids):
|
||||
merge_ids = []
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
level2ids = {}
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
ids = CIRelationManager.get_ancestor_ids(ids, 1)
|
||||
ids, _level2ids = CIRelationManager.get_ancestor_ids(ids, 1)
|
||||
|
||||
if _level2ids.get(2):
|
||||
level2ids[level + 1] = _level2ids[2]
|
||||
|
||||
if level in self.level:
|
||||
merge_ids.extend(ids)
|
||||
if level in level2ids and level2ids[level]:
|
||||
merge_ids.extend(set(ids) & set(level2ids[level]))
|
||||
else:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
return merge_ids
|
||||
|
||||
@@ -64,7 +124,7 @@ class Search(object):
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||
|
||||
merge_ids = self._get_ids() if not self.reverse else self._get_reverse_ids()
|
||||
merge_ids = self._get_ids(ids) if not self.reverse else self._get_reverse_ids(ids)
|
||||
|
||||
if not self.orig_query or ("_type:" not in self.orig_query
|
||||
and "type_id:" not in self.orig_query
|
||||
@@ -76,11 +136,11 @@ class Search(object):
|
||||
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
|
||||
else:
|
||||
type_ids.extend(CITypeRelationManager.get_parent_type_ids(ci.type_id, level))
|
||||
type_ids = list(set(type_ids))
|
||||
type_ids = set(type_ids)
|
||||
if self.orig_query:
|
||||
self.orig_query = "_type:({0}),{1}".format(";".join(list(map(str, type_ids))), self.orig_query)
|
||||
self.orig_query = "_type:({0}),{1}".format(";".join(map(str, type_ids)), self.orig_query)
|
||||
else:
|
||||
self.orig_query = "_type:({0})".format(";".join(list(map(str, type_ids))))
|
||||
self.orig_query = "_type:({0})".format(";".join(map(str, type_ids)))
|
||||
|
||||
if not merge_ids:
|
||||
# cis, counter, total, self.page, numfound, facet_
|
||||
@@ -105,35 +165,65 @@ class Search(object):
|
||||
|
||||
def statistics(self, type_ids):
|
||||
self.level = int(self.level)
|
||||
_tmp = []
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
for lv in range(0, self.level):
|
||||
if not lv:
|
||||
if type_ids and lv == self.level - 1:
|
||||
_tmp = []
|
||||
level2ids = {}
|
||||
for lv in range(1, self.level + 1):
|
||||
level2ids[lv] = []
|
||||
|
||||
if lv == 1:
|
||||
if not self.has_m2m:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
if not self.ancestor_ids:
|
||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
level2ids[lv] = [[i] for i in key]
|
||||
|
||||
if not key:
|
||||
_tmp = []
|
||||
continue
|
||||
|
||||
if type_ids and lv == self.level:
|
||||
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
||||
(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))))
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))))
|
||||
else:
|
||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))
|
||||
[i or '{}' for i in rd.get(key, prefix) or []]))
|
||||
|
||||
else:
|
||||
for idx, item in enumerate(_tmp):
|
||||
if item:
|
||||
if type_ids and lv == self.level - 1:
|
||||
__tmp = list(
|
||||
map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
if not self.has_m2m:
|
||||
key, prefix = [i[0] for i in item], REDIS_PREFIX_CI_RELATION
|
||||
else:
|
||||
key = list(set(['{},{}'.format(j, i[0]) for i in item for j in level2ids[lv - 1][idx]]))
|
||||
prefix = REDIS_PREFIX_CI_RELATION2
|
||||
|
||||
__tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
level2ids[lv].append(key)
|
||||
|
||||
if key:
|
||||
if type_ids and lv == self.level:
|
||||
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
else:
|
||||
__tmp = map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(key, prefix) or []))
|
||||
else:
|
||||
__tmp = []
|
||||
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
level2ids[lv].append([])
|
||||
|
||||
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ import api.models.cmdb as model
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
|
||||
TIME_RE = re.compile(r"^20|21|22|23|[0-1]\d:[0-5]\d:[0-5]\d$")
|
||||
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
|
||||
|
||||
|
||||
def string2int(x):
|
||||
|
@@ -94,8 +94,13 @@ class AttributeValueManager(object):
|
||||
@staticmethod
|
||||
def _check_is_choice(attr, value_type, value):
|
||||
choice_values = AttributeManager.get_choice_values(attr.id, value_type, attr.choice_web_hook, attr.choice_other)
|
||||
if str(value) not in list(map(str, [i[0] for i in choice_values])):
|
||||
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||
if value_type == ValueTypeEnum.FLOAT:
|
||||
if float(value) not in list(map(float, [i[0] for i in choice_values])):
|
||||
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||
|
||||
else:
|
||||
if str(value) not in list(map(str, [i[0] for i in choice_values])):
|
||||
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||
|
||||
@staticmethod
|
||||
def _check_is_unique(value_table, attr, ci_id, type_id, value):
|
||||
|
@@ -1,14 +1,24 @@
|
||||
from flask import abort
|
||||
import copy
|
||||
import json
|
||||
|
||||
from flask import abort, current_app
|
||||
from ldap3 import Connection
|
||||
from ldap3 import Server
|
||||
from ldap3.core.exceptions import LDAPBindError, LDAPSocketOpenError
|
||||
from ldap3 import AUTO_BIND_NO_TLS
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import CommonData
|
||||
from api.lib.utils import AESCrypto
|
||||
from api.lib.common_setting.const import AuthCommonConfig, AuthenticateType, AuthCommonConfigAutoRedirect, TestType
|
||||
|
||||
|
||||
class CommonDataCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_data_by_type(data_type):
|
||||
CommonDataCRUD.check_auth_type(data_type)
|
||||
return CommonData.get_by(data_type=data_type)
|
||||
|
||||
@staticmethod
|
||||
@@ -18,6 +28,8 @@ class CommonDataCRUD(object):
|
||||
@staticmethod
|
||||
def create_new_data(data_type, **kwargs):
|
||||
try:
|
||||
CommonDataCRUD.check_auth_type(data_type)
|
||||
|
||||
return CommonData.create(data_type=data_type, **kwargs)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
@@ -29,6 +41,7 @@ class CommonDataCRUD(object):
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
CommonDataCRUD.check_auth_type(existed.data_type)
|
||||
return existed.update(**kwargs)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
@@ -40,7 +53,230 @@ class CommonDataCRUD(object):
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
CommonDataCRUD.check_auth_type(existed.data_type)
|
||||
existed.soft_delete()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
@staticmethod
|
||||
def check_auth_type(data_type):
|
||||
if data_type in list(AuthenticateType.all()) + [AuthCommonConfig]:
|
||||
abort(400, ErrFormat.common_data_not_support_auth_type.format(data_type))
|
||||
|
||||
@staticmethod
|
||||
def set_auth_auto_redirect_enable(_value: int):
|
||||
existed = CommonData.get_by(first=True, data_type=AuthCommonConfig, to_dict=False)
|
||||
if not existed:
|
||||
CommonDataCRUD.create_new_data(AuthCommonConfig, data={AuthCommonConfigAutoRedirect: _value})
|
||||
else:
|
||||
data = existed.data
|
||||
data = copy.deepcopy(existed.data) if data else {}
|
||||
data[AuthCommonConfigAutoRedirect] = _value
|
||||
CommonDataCRUD.update_data(existed.id, data=data)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_auth_auto_redirect_enable():
|
||||
existed = CommonData.get_by(first=True, data_type=AuthCommonConfig)
|
||||
if not existed:
|
||||
return 0
|
||||
data = existed.get('data', {})
|
||||
if not data:
|
||||
return 0
|
||||
return data.get(AuthCommonConfigAutoRedirect, 0)
|
||||
|
||||
|
||||
class AuthenticateDataCRUD(object):
|
||||
common_type_list = [AuthCommonConfig]
|
||||
|
||||
def __init__(self, _type):
|
||||
self._type = _type
|
||||
self.record = None
|
||||
self.decrypt_data = {}
|
||||
|
||||
def get_support_type_list(self):
|
||||
return list(AuthenticateType.all()) + self.common_type_list
|
||||
|
||||
def get(self):
|
||||
if not self.decrypt_data:
|
||||
self.decrypt_data = self.get_decrypt_data()
|
||||
|
||||
return self.decrypt_data
|
||||
|
||||
def get_by_key(self, _key):
|
||||
if not self.decrypt_data:
|
||||
self.decrypt_data = self.get_decrypt_data()
|
||||
|
||||
return self.decrypt_data.get(_key, None)
|
||||
|
||||
def get_record(self, to_dict=False) -> CommonData:
|
||||
return CommonData.get_by(first=True, data_type=self._type, to_dict=to_dict)
|
||||
|
||||
def get_record_with_decrypt(self) -> dict:
|
||||
record = CommonData.get_by(first=True, data_type=self._type, to_dict=True)
|
||||
if not record:
|
||||
return {}
|
||||
data = self.get_decrypt_dict(record.get('data', ''))
|
||||
record['data'] = data
|
||||
return record
|
||||
|
||||
def get_decrypt_dict(self, data):
|
||||
decrypt_str = self.decrypt(data)
|
||||
try:
|
||||
return json.loads(decrypt_str)
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
|
||||
def get_decrypt_data(self) -> dict:
|
||||
self.record = self.get_record()
|
||||
if not self.record:
|
||||
return self.get_from_config()
|
||||
return self.get_decrypt_dict(self.record.data)
|
||||
|
||||
def get_from_config(self):
|
||||
return current_app.config.get(self._type, {})
|
||||
|
||||
def check_by_type(self) -> None:
|
||||
existed = self.get_record()
|
||||
if existed:
|
||||
abort(400, ErrFormat.common_data_already_existed.format(self._type))
|
||||
|
||||
def create(self, data) -> CommonData:
|
||||
self.check_by_type()
|
||||
encrypt = data.pop('encrypt', None)
|
||||
if encrypt is False:
|
||||
return CommonData.create(data_type=self._type, data=data)
|
||||
encrypted_data = self.encrypt(data)
|
||||
try:
|
||||
return CommonData.create(data_type=self._type, data=encrypted_data)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
def update_by_record(self, record, data) -> CommonData:
|
||||
encrypt = data.pop('encrypt', None)
|
||||
if encrypt is False:
|
||||
return record.update(data=data)
|
||||
encrypted_data = self.encrypt(data)
|
||||
try:
|
||||
return record.update(data=encrypted_data)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
def update(self, _id, data) -> CommonData:
|
||||
existed = CommonData.get_by(first=True, to_dict=False, id=_id)
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
|
||||
return self.update_by_record(existed, data)
|
||||
|
||||
@staticmethod
|
||||
def delete(_id) -> None:
|
||||
existed = CommonData.get_by(first=True, to_dict=False, id=_id)
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
existed.soft_delete()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
@staticmethod
|
||||
def encrypt(data) -> str:
|
||||
if type(data) is dict:
|
||||
try:
|
||||
data = json.dumps(data)
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
return AESCrypto().encrypt(data)
|
||||
|
||||
@staticmethod
|
||||
def decrypt(data) -> str:
|
||||
return AESCrypto().decrypt(data)
|
||||
|
||||
@staticmethod
|
||||
def get_enable_list():
|
||||
all_records = CommonData.query.filter(
|
||||
CommonData.data_type.in_(AuthenticateType.all()),
|
||||
CommonData.deleted == 0
|
||||
).all()
|
||||
enable_list = []
|
||||
for auth_type in AuthenticateType.all():
|
||||
record = list(filter(lambda x: x.data_type == auth_type, all_records))
|
||||
if not record:
|
||||
config = current_app.config.get(auth_type, None)
|
||||
if not config:
|
||||
continue
|
||||
|
||||
if config.get('enable', False):
|
||||
enable_list.append(dict(
|
||||
auth_type=auth_type,
|
||||
))
|
||||
|
||||
continue
|
||||
|
||||
try:
|
||||
decrypt_data = json.loads(AuthenticateDataCRUD.decrypt(record[0].data))
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
continue
|
||||
|
||||
if decrypt_data.get('enable', 0) == 1:
|
||||
enable_list.append(dict(
|
||||
auth_type=auth_type,
|
||||
))
|
||||
|
||||
auth_auto_redirect = CommonDataCRUD.get_auth_auto_redirect_enable()
|
||||
|
||||
return dict(
|
||||
enable_list=enable_list,
|
||||
auth_auto_redirect=auth_auto_redirect,
|
||||
)
|
||||
|
||||
def test(self, test_type, data):
|
||||
type_lower = self._type.lower()
|
||||
func_name = f'test_{type_lower}'
|
||||
if hasattr(self, func_name):
|
||||
try:
|
||||
return getattr(self, f'test_{type_lower}')(test_type, data)
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
abort(400, ErrFormat.not_support_test.format(self._type))
|
||||
|
||||
@staticmethod
|
||||
def test_ldap(test_type, data):
|
||||
ldap_server = data.get('ldap_server')
|
||||
ldap_user_dn = data.get('ldap_user_dn', '{}')
|
||||
|
||||
server = Server(ldap_server, connect_timeout=2)
|
||||
if not server.check_availability():
|
||||
raise Exception(ErrFormat.ldap_server_connect_not_available)
|
||||
else:
|
||||
if test_type == TestType.Connect:
|
||||
return True
|
||||
|
||||
username = data.get('username', None)
|
||||
if not username:
|
||||
raise Exception(ErrFormat.ldap_test_username_required)
|
||||
user = ldap_user_dn.format(username)
|
||||
password = data.get('password', None)
|
||||
|
||||
try:
|
||||
Connection(server, user=user, password=password, auto_bind=AUTO_BIND_NO_TLS)
|
||||
except LDAPBindError:
|
||||
ldap_domain = data.get('ldap_domain')
|
||||
user_with_domain = f"{username}@{ldap_domain}"
|
||||
try:
|
||||
Connection(server, user=user_with_domain, password=password, auto_bind=AUTO_BIND_NO_TLS)
|
||||
except Exception as e:
|
||||
raise Exception(ErrFormat.ldap_test_unknown_error.format(str(e)))
|
||||
|
||||
except LDAPSocketOpenError:
|
||||
raise Exception(ErrFormat.ldap_server_connect_timeout)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(ErrFormat.ldap_test_unknown_error.format(str(e)))
|
||||
|
||||
return True
|
||||
|
@@ -1,4 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from api.extensions import cache
|
||||
from api.models.common_setting import CompanyInfo
|
||||
|
||||
@@ -11,6 +13,7 @@ class CompanyInfoCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
CompanyInfoCRUD.check_data(**kwargs)
|
||||
res = CompanyInfo.create(**kwargs)
|
||||
CompanyInfoCache.refresh(res.info)
|
||||
return res
|
||||
@@ -22,10 +25,26 @@ class CompanyInfoCRUD(object):
|
||||
if not existed:
|
||||
existed = CompanyInfoCRUD.create(**kwargs)
|
||||
else:
|
||||
CompanyInfoCRUD.check_data(**kwargs)
|
||||
existed = existed.update(**kwargs)
|
||||
CompanyInfoCache.refresh(existed.info)
|
||||
return existed
|
||||
|
||||
@staticmethod
|
||||
def check_data(**kwargs):
|
||||
info = kwargs.get('info', {})
|
||||
info['messenger'] = CompanyInfoCRUD.check_messenger(info.get('messenger', None))
|
||||
|
||||
kwargs['info'] = info
|
||||
|
||||
@staticmethod
|
||||
def check_messenger(messenger):
|
||||
if not messenger:
|
||||
return messenger
|
||||
|
||||
parsed_url = urlparse(messenger)
|
||||
return f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
|
||||
|
||||
class CompanyInfoCache(object):
|
||||
key = 'CompanyInfoCache::'
|
||||
@@ -41,4 +60,4 @@ class CompanyInfoCache(object):
|
||||
|
||||
@classmethod
|
||||
def refresh(cls, info):
|
||||
cache.set(cls.key, info)
|
||||
cache.set(cls.key, info)
|
||||
|
@@ -19,3 +19,19 @@ BotNameMap = {
|
||||
'feishuApp': 'feishuBot',
|
||||
'dingdingApp': 'dingdingBot',
|
||||
}
|
||||
|
||||
|
||||
class AuthenticateType(BaseEnum):
|
||||
CAS = 'CAS'
|
||||
OAUTH2 = 'OAUTH2'
|
||||
OIDC = 'OIDC'
|
||||
LDAP = 'LDAP'
|
||||
|
||||
|
||||
AuthCommonConfig = 'AuthCommonConfig'
|
||||
AuthCommonConfigAutoRedirect = 'auto_redirect'
|
||||
|
||||
|
||||
class TestType(BaseEnum):
|
||||
Connect = 'connect'
|
||||
Login = 'login'
|
||||
|
@@ -24,7 +24,15 @@ def get_all_department_list(to_dict=True):
|
||||
*criterion
|
||||
).order_by(Department.department_id.asc())
|
||||
results = query.all()
|
||||
return [r.to_dict() for r in results] if to_dict else results
|
||||
if to_dict:
|
||||
datas = []
|
||||
for r in results:
|
||||
d = r.to_dict()
|
||||
if r.department_id == 0:
|
||||
d['department_name'] = ErrFormat.company_wide
|
||||
datas.append(d)
|
||||
return datas
|
||||
return results
|
||||
|
||||
|
||||
def get_all_employee_list(block=0, to_dict=True):
|
||||
@@ -101,6 +109,7 @@ class DepartmentTree(object):
|
||||
employees = self.get_employees_by_d_id(department_id)
|
||||
|
||||
top_d['employees'] = employees
|
||||
top_d['department_name'] = ErrFormat.company_wide
|
||||
if len(sub_deps) == 0:
|
||||
top_d[sub_departments_column_name] = []
|
||||
d_list.append(top_d)
|
||||
@@ -246,7 +255,7 @@ class DepartmentCRUD(object):
|
||||
return abort(400, ErrFormat.acl_update_role_failed.format(str(e)))
|
||||
|
||||
try:
|
||||
existed.update(**kwargs)
|
||||
return existed.update(**kwargs)
|
||||
except Exception as e:
|
||||
return abort(400, str(e))
|
||||
|
||||
@@ -313,6 +322,7 @@ class DepartmentCRUD(object):
|
||||
tree_list = []
|
||||
|
||||
for top_d in top_deps:
|
||||
top_d['department_name'] = ErrFormat.company_wide
|
||||
tree = Tree()
|
||||
identifier_root = top_d['department_id']
|
||||
tree.create_node(
|
||||
@@ -383,6 +393,9 @@ class DepartmentCRUD(object):
|
||||
|
||||
d['employee_count'] = len(list(filter(lambda e: e['department_id'] in d_ids, all_employee_list)))
|
||||
|
||||
if int(department_parent_id) == -1:
|
||||
d['department_name'] = ErrFormat.company_wide
|
||||
|
||||
return all_departments, department_id_list
|
||||
|
||||
@staticmethod
|
||||
|
@@ -15,10 +15,13 @@ from wtforms import validators
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.const import COMMON_SETTING_QUEUE, OperatorType
|
||||
from api.lib.common_setting.const import OperatorType
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
from api.tasks.common_setting import refresh_employee_acl_info, edit_employee_department_in_acl
|
||||
|
||||
acl_user_columns = [
|
||||
'email',
|
||||
'mobile',
|
||||
@@ -137,7 +140,9 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def add(**kwargs):
|
||||
try:
|
||||
return CreateEmployee().create_single(**kwargs)
|
||||
res = CreateEmployee().create_single(**kwargs)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
return res
|
||||
except Exception as e:
|
||||
abort(400, str(e))
|
||||
|
||||
@@ -164,10 +169,9 @@ class EmployeeCRUD(object):
|
||||
existed.update(**kwargs)
|
||||
|
||||
if len(e_list) > 0:
|
||||
from api.tasks.common_setting import edit_employee_department_in_acl
|
||||
edit_employee_department_in_acl.apply_async(
|
||||
args=(e_list, new_department_id, current_user.uid),
|
||||
queue=COMMON_SETTING_QUEUE
|
||||
queue=CMDB_QUEUE
|
||||
)
|
||||
|
||||
return existed
|
||||
@@ -291,7 +295,9 @@ class EmployeeCRUD(object):
|
||||
employees = []
|
||||
for r in pagination.items:
|
||||
d = r.Employee.to_dict()
|
||||
d['department_name'] = r.Department.department_name
|
||||
d['department_name'] = r.Department.department_name if r.Department else ''
|
||||
if r.Employee.department_id == 0:
|
||||
d['department_name'] = ErrFormat.company_wide
|
||||
employees.append(d)
|
||||
|
||||
return {
|
||||
@@ -437,7 +443,7 @@ class EmployeeCRUD(object):
|
||||
employees = []
|
||||
for r in pagination.items:
|
||||
d = r.Employee.to_dict()
|
||||
d['department_name'] = r.Department.department_name
|
||||
d['department_name'] = r.Department.department_name if r.Department else ''
|
||||
employees.append(d)
|
||||
|
||||
return {
|
||||
@@ -563,6 +569,7 @@ class EmployeeCRUD(object):
|
||||
for column in direct_columns:
|
||||
tmp[column] = d.get(column, '')
|
||||
notice_info = d.get('notice_info', {})
|
||||
notice_info = copy.deepcopy(notice_info) if notice_info else {}
|
||||
tmp.update(**notice_info)
|
||||
results.append(tmp)
|
||||
return results
|
||||
@@ -570,6 +577,7 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def import_employee(employee_list):
|
||||
res = CreateEmployee().batch_create(employee_list)
|
||||
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -686,6 +694,27 @@ class EmployeeCRUD(object):
|
||||
else:
|
||||
abort(400, ErrFormat.column_name_not_support)
|
||||
|
||||
@staticmethod
|
||||
def update_last_login_by_uid(uid, last_login=None):
|
||||
employee = Employee.get_by(acl_uid=uid, first=True, to_dict=False)
|
||||
if not employee:
|
||||
return
|
||||
if last_login:
|
||||
try:
|
||||
last_login = datetime.strptime(last_login, '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as e:
|
||||
last_login = datetime.now()
|
||||
else:
|
||||
last_login = datetime.now()
|
||||
|
||||
try:
|
||||
employee.update(
|
||||
last_login=last_login
|
||||
)
|
||||
return last_login
|
||||
except Exception as e:
|
||||
return
|
||||
|
||||
|
||||
def get_user_map(key='uid', acl=None):
|
||||
"""
|
||||
@@ -726,6 +755,7 @@ class CreateEmployee(object):
|
||||
try:
|
||||
existed = self.check_acl_user(user_data)
|
||||
if not existed:
|
||||
user_data['add_from'] = 'common'
|
||||
return self.acl.create_user(user_data)
|
||||
return existed
|
||||
except Exception as e:
|
||||
|
@@ -1,65 +1,82 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from api.lib.resp_format import CommonErrFormat
|
||||
|
||||
|
||||
class ErrFormat(CommonErrFormat):
|
||||
company_info_is_already_existed = "公司信息已存在!无法创建"
|
||||
company_info_is_already_existed = _l("Company info already existed") # 公司信息已存在!无法创建
|
||||
|
||||
no_file_part = "没有文件部分"
|
||||
file_is_required = "文件是必须的"
|
||||
no_file_part = _l("No file part") # 没有文件部分
|
||||
file_is_required = _l("File is required") # 文件是必须的
|
||||
file_not_found = _l("File not found") # 文件不存在
|
||||
file_type_not_allowed = _l("File type not allowed") # 文件类型不允许
|
||||
upload_failed = _l("Upload failed: {}") # 上传失败: {}
|
||||
|
||||
direct_supervisor_is_not_self = "直属上级不能是自己"
|
||||
parent_department_is_not_self = "上级部门不能是自己"
|
||||
employee_list_is_empty = "员工列表为空"
|
||||
direct_supervisor_is_not_self = _l("Direct supervisor is not self") # 直属上级不能是自己
|
||||
parent_department_is_not_self = _l("Parent department is not self") # 上级部门不能是自己
|
||||
employee_list_is_empty = _l("Employee list is empty") # 员工列表为空
|
||||
|
||||
column_name_not_support = "不支持的列名"
|
||||
password_is_required = "密码不能为空"
|
||||
employee_acl_rid_is_zero = "员工ACL角色ID不能为0"
|
||||
column_name_not_support = _l("Column name not support") # 不支持的列名
|
||||
password_is_required = _l("Password is required") # 密码是必须的
|
||||
employee_acl_rid_is_zero = _l("Employee acl rid is zero") # 员工ACL角色ID不能为0
|
||||
|
||||
generate_excel_failed = "生成excel失败: {}"
|
||||
rename_columns_failed = "字段转换为中文失败: {}"
|
||||
cannot_block_this_employee_is_other_direct_supervisor = "该员工是其他员工的直属上级, 不能禁用"
|
||||
cannot_block_this_employee_is_department_manager = "该员工是部门负责人, 不能禁用"
|
||||
employee_id_not_found = "员工ID [{}] 不存在"
|
||||
value_is_required = "值是必须的"
|
||||
email_already_exists = "邮箱 [{}] 已存在"
|
||||
query_column_none_keep_value_empty = "查询 {} 空值时请保持value为空"
|
||||
not_support_operator = "不支持的操作符: {}"
|
||||
not_support_relation = "不支持的关系: {}"
|
||||
conditions_field_missing = "conditions内元素字段缺失,请检查!"
|
||||
datetime_format_error = "{} 格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
||||
department_level_relation_error = "部门层级关系不正确"
|
||||
delete_reserved_department_name = "保留部门,无法删除!"
|
||||
department_id_is_required = "部门ID是必须的"
|
||||
department_list_is_required = "部门列表是必须的"
|
||||
cannot_to_be_parent_department = "{} 不能设置为上级部门"
|
||||
department_id_not_found = "部门ID [{}] 不存在"
|
||||
parent_department_id_must_more_than_zero = "上级部门ID必须大于0"
|
||||
department_name_already_exists = "部门名称 [{}] 已存在"
|
||||
new_department_is_none = "新部门是空的"
|
||||
generate_excel_failed = _l("Generate excel failed: {}") # 生成excel失败: {}
|
||||
rename_columns_failed = _l("Rename columns failed: {}") # 重命名字段失败: {}
|
||||
cannot_block_this_employee_is_other_direct_supervisor = _l(
|
||||
"Cannot block this employee is other direct supervisor") # 该员工是其他员工的直属上级, 不能禁用
|
||||
cannot_block_this_employee_is_department_manager = _l(
|
||||
"Cannot block this employee is department manager") # 该员工是部门负责人, 不能禁用
|
||||
employee_id_not_found = _l("Employee id [{}] not found") # 员工ID [{}] 不存在
|
||||
value_is_required = _l("Value is required") # 值是必须的
|
||||
email_already_exists = _l("Email already exists") # 邮箱已存在
|
||||
query_column_none_keep_value_empty = _l("Query {} none keep value empty") # 查询 {} 空值时请保持value为空"
|
||||
not_support_operator = _l("Not support operator: {}") # 不支持的操作符: {}
|
||||
not_support_relation = _l("Not support relation: {}") # 不支持的关系: {}
|
||||
conditions_field_missing = _l("Conditions field missing") # conditions内元素字段缺失,请检查!
|
||||
datetime_format_error = _l("Datetime format error: {}") # {} 格式错误,应该为:%Y-%m-%d %H:%M:%S
|
||||
department_level_relation_error = _l("Department level relation error") # 部门层级关系不正确
|
||||
delete_reserved_department_name = _l("Delete reserved department name") # 保留部门,无法删除!
|
||||
department_id_is_required = _l("Department id is required") # 部门ID是必须的
|
||||
department_list_is_required = _l("Department list is required") # 部门列表是必须的
|
||||
cannot_to_be_parent_department = _l("{} Cannot to be parent department") # 不能设置为上级部门
|
||||
department_id_not_found = _l("Department id [{}] not found") # 部门ID [{}] 不存在
|
||||
parent_department_id_must_more_than_zero = _l("Parent department id must more than zero") # 上级部门ID必须大于0
|
||||
department_name_already_exists = _l("Department name [{}] already exists") # 部门名称 [{}] 已存在
|
||||
new_department_is_none = _l("New department is none") # 新部门是空的
|
||||
|
||||
acl_edit_user_failed = "ACL 修改用户失败: {}"
|
||||
acl_uid_not_found = "ACL 用户UID [{}] 不存在"
|
||||
acl_add_user_failed = "ACL 添加用户失败: {}"
|
||||
acl_add_role_failed = "ACL 添加角色失败: {}"
|
||||
acl_update_role_failed = "ACL 更新角色失败: {}"
|
||||
acl_get_all_users_failed = "ACL 获取所有用户失败: {}"
|
||||
acl_remove_user_from_role_failed = "ACL 从角色中移除用户失败: {}"
|
||||
acl_add_user_to_role_failed = "ACL 添加用户到角色失败: {}"
|
||||
acl_import_user_failed = "ACL 导入用户[{}]失败: {}"
|
||||
acl_edit_user_failed = _l("ACL edit user failed: {}") # ACL 修改用户失败: {}
|
||||
acl_uid_not_found = _l("ACL uid not found: {}") # ACL 用户UID [{}] 不存在
|
||||
acl_add_user_failed = _l("ACL add user failed: {}") # ACL 添加用户失败: {}
|
||||
acl_add_role_failed = _l("ACL add role failed: {}") # ACL 添加角色失败: {}
|
||||
acl_update_role_failed = _l("ACL update role failed: {}") # ACL 更新角色失败: {}
|
||||
acl_get_all_users_failed = _l("ACL get all users failed: {}") # ACL 获取所有用户失败: {}
|
||||
acl_remove_user_from_role_failed = _l("ACL remove user from role failed: {}") # ACL 从角色中移除用户失败: {}
|
||||
acl_add_user_to_role_failed = _l("ACL add user to role failed: {}") # ACL 添加用户到角色失败: {}
|
||||
acl_import_user_failed = _l("ACL import user failed: {}") # ACL 导入用户失败: {}
|
||||
|
||||
nickname_is_required = "用户名不能为空"
|
||||
username_is_required = "username不能为空"
|
||||
email_is_required = "邮箱不能为空"
|
||||
email_format_error = "邮箱格式错误"
|
||||
email_send_timeout = "邮件发送超时"
|
||||
nickname_is_required = _l("Nickname is required") # 昵称不能为空
|
||||
username_is_required = _l("Username is required") # 用户名不能为空
|
||||
email_is_required = _l("Email is required") # 邮箱不能为空
|
||||
email_format_error = _l("Email format error") # 邮箱格式错误
|
||||
email_send_timeout = _l("Email send timeout") # 邮件发送超时
|
||||
|
||||
common_data_not_found = "ID {} 找不到记录"
|
||||
notice_platform_existed = "{} 已存在"
|
||||
notice_not_existed = "{} 配置项不存在"
|
||||
notice_please_config_messenger_first = "请先配置 messenger"
|
||||
notice_bind_err_with_empty_mobile = "绑定失败,手机号为空"
|
||||
notice_bind_failed = "绑定失败: {}"
|
||||
notice_bind_success = "绑定成功"
|
||||
notice_remove_bind_success = "解绑成功"
|
||||
common_data_not_found = _l("Common data not found {} ") # ID {} 找不到记录
|
||||
common_data_already_existed = _l("Common data {} already existed") # {} 已存在
|
||||
notice_platform_existed = _l("Notice platform {} existed") # {} 已存在
|
||||
notice_not_existed = _l("Notice {} not existed") # {} 配置项不存在
|
||||
notice_please_config_messenger_first = _l("Notice please config messenger first") # 请先配置messenger URL
|
||||
notice_bind_err_with_empty_mobile = _l("Notice bind err with empty mobile") # 绑定错误,手机号为空
|
||||
notice_bind_failed = _l("Notice bind failed: {}") # 绑定失败: {}
|
||||
notice_bind_success = _l("Notice bind success") # 绑定成功
|
||||
notice_remove_bind_success = _l("Notice remove bind success") # 解绑成功
|
||||
|
||||
not_support_test = _l("Not support test type: {}") # 不支持的测试类型: {}
|
||||
not_support_auth_type = _l("Not support auth type: {}") # 不支持的认证类型: {}
|
||||
ldap_server_connect_timeout = _l("LDAP server connect timeout") # LDAP服务器连接超时
|
||||
ldap_server_connect_not_available = _l("LDAP server connect not available") # LDAP服务器连接不可用
|
||||
ldap_test_unknown_error = _l("LDAP test unknown error: {}") # LDAP测试未知错误: {}
|
||||
common_data_not_support_auth_type = _l("Common data not support auth type: {}") # 通用数据不支持auth类型: {}
|
||||
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
||||
|
||||
company_wide = _l("Company wide") # 全公司
|
||||
|
@@ -1,6 +1,14 @@
|
||||
import base64
|
||||
import uuid
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from flask import abort, current_app
|
||||
import lz4.frame
|
||||
|
||||
from api.lib.common_setting.utils import get_cur_time_str
|
||||
from api.models.common_setting import CommonFile
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
|
||||
|
||||
def allowed_file(filename, allowed_extensions):
|
||||
@@ -14,3 +22,73 @@ def generate_new_file_name(name):
|
||||
cur_str = get_cur_time_str('_')
|
||||
|
||||
return f"{prev_name}_{cur_str}_{uid}.{ext}"
|
||||
|
||||
|
||||
class CommonFileCRUD:
|
||||
@staticmethod
|
||||
def add_file(**kwargs):
|
||||
return CommonFile.create(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_file(file_name, to_str=False):
|
||||
existed = CommonFile.get_by(file_name=file_name, first=True, to_dict=False)
|
||||
if not existed:
|
||||
abort(400, ErrFormat.file_not_found)
|
||||
|
||||
uncompressed_data = lz4.frame.decompress(existed.binary)
|
||||
|
||||
return base64.b64encode(uncompressed_data).decode('utf-8') if to_str else BytesIO(uncompressed_data)
|
||||
|
||||
@staticmethod
|
||||
def sync_file_to_db():
|
||||
for p in ['UPLOAD_DIRECTORY_FULL']:
|
||||
upload_path = current_app.config.get(p, None)
|
||||
if not upload_path:
|
||||
continue
|
||||
for root, dirs, files in os.walk(upload_path):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
if not os.path.isfile(file_path):
|
||||
continue
|
||||
|
||||
existed = CommonFile.get_by(file_name=file, first=True, to_dict=False)
|
||||
if existed:
|
||||
continue
|
||||
with open(file_path, 'rb') as f:
|
||||
data = f.read()
|
||||
compressed_data = lz4.frame.compress(data)
|
||||
try:
|
||||
CommonFileCRUD.add_file(
|
||||
origin_name=file,
|
||||
file_name=file,
|
||||
binary=compressed_data
|
||||
)
|
||||
|
||||
current_app.logger.info(f'sync file {file} to db')
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'sync file {file} to db error: {e}')
|
||||
|
||||
def get_file_binary_str(self, file_name):
|
||||
return self.get_file(file_name, True)
|
||||
|
||||
def save_str_to_file(self, file_name, str_data):
|
||||
try:
|
||||
self.get_file(file_name)
|
||||
current_app.logger.info(f'file {file_name} already exists')
|
||||
return
|
||||
except Exception as e:
|
||||
# file not found
|
||||
pass
|
||||
|
||||
bytes_data = base64.b64decode(str_data)
|
||||
compressed_data = lz4.frame.compress(bytes_data)
|
||||
|
||||
try:
|
||||
self.add_file(
|
||||
origin_name=file_name,
|
||||
file_name=file_name,
|
||||
binary=compressed_data
|
||||
)
|
||||
current_app.logger.info(f'save_str_to_file {file_name} success')
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"save_str_to_file error: {e}")
|
||||
|
@@ -94,7 +94,7 @@ class CRUDMixin(FormatMixin):
|
||||
if any((isinstance(_id, six.string_types) and _id.isdigit(),
|
||||
isinstance(_id, (six.integer_types, float))), ):
|
||||
obj = getattr(cls, "query").get(int(_id))
|
||||
if obj and not obj.deleted:
|
||||
if obj and not getattr(obj, 'deleted', False):
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
|
@@ -117,15 +117,15 @@ class ACLManager(object):
|
||||
if group:
|
||||
PermissionCRUD.grant(role.id, permissions, group_id=group.id)
|
||||
|
||||
def grant_resource_to_role_by_rid(self, name, rid, resource_type_name=None, permissions=None):
|
||||
def grant_resource_to_role_by_rid(self, name, rid, resource_type_name=None, permissions=None, rebuild=True):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
|
||||
if resource:
|
||||
PermissionCRUD.grant(rid, permissions, resource_id=resource.id)
|
||||
PermissionCRUD.grant(rid, permissions, resource_id=resource.id, rebuild=rebuild)
|
||||
else:
|
||||
group = self._get_resource_group(name)
|
||||
if group:
|
||||
PermissionCRUD.grant(rid, permissions, group_id=group.id)
|
||||
PermissionCRUD.grant(rid, permissions, group_id=group.id, rebuild=rebuild)
|
||||
|
||||
def revoke_resource_from_role(self, name, role, resource_type_name=None, permissions=None):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
@@ -138,20 +138,20 @@ class ACLManager(object):
|
||||
if group:
|
||||
PermissionCRUD.revoke(role.id, permissions, group_id=group.id)
|
||||
|
||||
def revoke_resource_from_role_by_rid(self, name, rid, resource_type_name=None, permissions=None):
|
||||
def revoke_resource_from_role_by_rid(self, name, rid, resource_type_name=None, permissions=None, rebuild=True):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
|
||||
if resource:
|
||||
PermissionCRUD.revoke(rid, permissions, resource_id=resource.id)
|
||||
PermissionCRUD.revoke(rid, permissions, resource_id=resource.id, rebuild=rebuild)
|
||||
else:
|
||||
group = self._get_resource_group(name)
|
||||
if group:
|
||||
PermissionCRUD.revoke(rid, permissions, group_id=group.id)
|
||||
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
|
||||
|
||||
def del_resource(self, name, resource_type_name=None):
|
||||
resource = self._get_resource(name, resource_type_name)
|
||||
if resource:
|
||||
ResourceCRUD.delete(resource.id)
|
||||
return ResourceCRUD.delete(resource.id)
|
||||
|
||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
||||
if is_app_admin(self.app_id):
|
||||
|
@@ -1,14 +1,19 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import itertools
|
||||
import json
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from flask import has_request_context, request
|
||||
from flask import has_request_context
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl import AppCache
|
||||
from api.models.acl import AuditLoginLog
|
||||
from api.models.acl import AuditPermissionLog
|
||||
from api.models.acl import AuditResourceLog
|
||||
from api.models.acl import AuditRoleLog
|
||||
@@ -283,6 +288,27 @@ class AuditCRUD(object):
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def search_login(_, q=None, page=1, page_size=10, start=None, end=None):
|
||||
query = db.session.query(AuditLoginLog)
|
||||
|
||||
if start:
|
||||
query = query.filter(AuditLoginLog.login_at >= start)
|
||||
if end:
|
||||
query = query.filter(AuditLoginLog.login_at <= end)
|
||||
|
||||
if q:
|
||||
query = query.filter(AuditLoginLog.username == q)
|
||||
|
||||
records = query.order_by(
|
||||
AuditLoginLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def add_role_log(cls, app_id, operate_type: AuditOperateType,
|
||||
scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict,
|
||||
@@ -348,3 +374,30 @@ class AuditCRUD(object):
|
||||
AuditTriggerLog.create(app_id=app_id, trigger_id=trigger_id, operate_uid=user_id,
|
||||
operate_type=operate_type.value,
|
||||
origin=origin, current=current, extra=extra, source=source.value)
|
||||
|
||||
@classmethod
|
||||
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None):
|
||||
if _id is not None:
|
||||
existed = AuditLoginLog.get_by_id(_id)
|
||||
if existed is not None:
|
||||
existed.update(logout_at=logout_at)
|
||||
return
|
||||
|
||||
payload = dict(username=username,
|
||||
is_ok=is_ok,
|
||||
description=description,
|
||||
logout_at=logout_at,
|
||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
||||
browser=request.headers.get('User-Agent'),
|
||||
)
|
||||
|
||||
if logout_at is None:
|
||||
payload['login_at'] = datetime.datetime.now()
|
||||
|
||||
try:
|
||||
from api.lib.common_setting.employee import EmployeeCRUD
|
||||
EmployeeCRUD.update_last_login_by_uid(current_user.uid)
|
||||
except:
|
||||
pass
|
||||
|
||||
return AuditLoginLog.create(**payload).id
|
||||
|
@@ -328,6 +328,8 @@ class ResourceCRUD(object):
|
||||
AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete,
|
||||
AuditScope.resource, resource.id, origin, {}, {})
|
||||
|
||||
return rebuilds
|
||||
|
||||
@classmethod
|
||||
def delete_by_name(cls, name, type_id, app_id):
|
||||
resource = Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) or abort(
|
||||
|
@@ -1,43 +1,50 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from api.lib.resp_format import CommonErrFormat
|
||||
|
||||
|
||||
class ErrFormat(CommonErrFormat):
|
||||
auth_only_with_app_token_failed = "应用 Token验证失败"
|
||||
session_invalid = "您不是应用管理员 或者 session失效(尝试一下退出重新登录)"
|
||||
login_succeed = _l("login successful") # 登录成功
|
||||
ldap_connection_failed = _l("Failed to connect to LDAP service") # 连接LDAP服务失败
|
||||
invalid_password = _l("Password verification failed") # 密码验证失败
|
||||
auth_only_with_app_token_failed = _l("Application Token verification failed") # 应用 Token验证失败
|
||||
# 您不是应用管理员 或者 session失效(尝试一下退出重新登录)
|
||||
session_invalid = _l(
|
||||
"You are not the application administrator or the session has expired (try logging out and logging in again)")
|
||||
|
||||
resource_type_not_found = "资源类型 {} 不存在!"
|
||||
resource_type_exists = "资源类型 {} 已经存在!"
|
||||
resource_type_cannot_delete = "因为该类型下有资源的存在, 不能删除!"
|
||||
resource_type_not_found = _l("Resource type {} does not exist!") # 资源类型 {} 不存在!
|
||||
resource_type_exists = _l("Resource type {} already exists!") # 资源类型 {} 已经存在!
|
||||
# 因为该类型下有资源的存在, 不能删除!
|
||||
resource_type_cannot_delete = _l("Because there are resources under this type, they cannot be deleted!")
|
||||
|
||||
user_not_found = "用户 {} 不存在!"
|
||||
user_exists = "用户 {} 已经存在!"
|
||||
role_not_found = "角色 {} 不存在!"
|
||||
role_exists = "角色 {} 已经存在!"
|
||||
global_role_not_found = "全局角色 {} 不存在!"
|
||||
global_role_exists = "全局角色 {} 已经存在!"
|
||||
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
|
||||
user_not_found = _l("User {} does not exist!") # 用户 {} 不存在!
|
||||
user_exists = _l("User {} already exists!") # 用户 {} 已经存在!
|
||||
role_not_found = _l("Role {} does not exist!") # 角色 {} 不存在!
|
||||
role_exists = _l("Role {} already exists!") # 角色 {} 已经存在!
|
||||
global_role_not_found = _l("Global role {} does not exist!") # 全局角色 {} 不存在!
|
||||
global_role_exists = _l("Global role {} already exists!") # 全局角色 {} 已经存在!
|
||||
|
||||
resource_no_permission = "您没有资源: {} 的 {} 权限"
|
||||
admin_required = "需要管理员权限"
|
||||
role_required = "需要角色: {}"
|
||||
resource_no_permission = _l("You do not have {} permission on resource: {}") # 您没有资源: {} 的 {} 权限
|
||||
admin_required = _l("Requires administrator permissions") # 需要管理员权限
|
||||
role_required = _l("Requires role: {}") # 需要角色: {}
|
||||
# 删除用户角色, 请在 用户管理 页面操作!
|
||||
user_role_delete_invalid = _l("To delete a user role, please operate on the User Management page!")
|
||||
|
||||
app_is_ready_existed = "应用 {} 已经存在"
|
||||
app_not_found = "应用 {} 不存在!"
|
||||
app_secret_invalid = "应用的Secret无效"
|
||||
app_is_ready_existed = _l("Application {} already exists") # 应用 {} 已经存在
|
||||
app_not_found = _l("Application {} does not exist!") # 应用 {} 不存在!
|
||||
app_secret_invalid = _l("The Secret is invalid") # 应用的Secret无效
|
||||
|
||||
resource_not_found = "资源 {} 不存在!"
|
||||
resource_exists = "资源 {} 已经存在!"
|
||||
resource_not_found = _l("Resource {} does not exist!") # 资源 {} 不存在!
|
||||
resource_exists = _l("Resource {} already exists!") # 资源 {} 已经存在!
|
||||
|
||||
resource_group_not_found = "资源组 {} 不存在!"
|
||||
resource_group_exists = "资源组 {} 已经存在!"
|
||||
resource_group_not_found = _l("Resource group {} does not exist!") # 资源组 {} 不存在!
|
||||
resource_group_exists = _l("Resource group {} already exists!") # 资源组 {} 已经存在!
|
||||
|
||||
inheritance_dead_loop = "继承检测到了死循环"
|
||||
role_relation_not_found = "角色关系 {} 不存在!"
|
||||
inheritance_dead_loop = _l("Inheritance detected infinite loop") # 继承检测到了死循环
|
||||
role_relation_not_found = _l("Role relationship {} does not exist!") # 角色关系 {} 不存在!
|
||||
|
||||
trigger_not_found = "触发器 {} 不存在!"
|
||||
trigger_exists = "触发器 {} 已经存在!"
|
||||
trigger_disabled = "触发器 {} 已经被禁用!"
|
||||
|
||||
invalid_password = "密码不正确!"
|
||||
trigger_not_found = _l("Trigger {} does not exist!") # 触发器 {} 不存在!
|
||||
trigger_exists = _l("Trigger {} already exists!") # 触发器 {} 已经存在!
|
||||
trigger_disabled = _l("Trigger {} has been disabled!") # Trigger {} has been disabled!
|
||||
|
@@ -41,6 +41,7 @@ class UserCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, **kwargs):
|
||||
add_from = kwargs.pop('add_from', None)
|
||||
existed = User.get_by(username=kwargs['username'])
|
||||
existed and abort(400, ErrFormat.user_exists.format(kwargs['username']))
|
||||
|
||||
@@ -62,10 +63,12 @@ class UserCRUD(object):
|
||||
AuditCRUD.add_role_log(None, AuditOperateType.create,
|
||||
AuditScope.user, user.uid, {}, user.to_dict(), {}, {}
|
||||
)
|
||||
from api.lib.common_setting.employee import EmployeeCRUD
|
||||
payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']}
|
||||
payload['rid'] = role.id
|
||||
EmployeeCRUD.add_employee_from_acl_created(**payload)
|
||||
|
||||
if add_from != 'common':
|
||||
from api.lib.common_setting.employee import EmployeeCRUD
|
||||
payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']}
|
||||
payload['rid'] = role.id
|
||||
EmployeeCRUD.add_employee_from_acl_created(**payload)
|
||||
|
||||
return user
|
||||
|
||||
|
@@ -93,6 +93,9 @@ def _auth_with_token():
|
||||
|
||||
|
||||
def _auth_with_ip_white_list():
|
||||
if request.url.endswith("acl/users/info"):
|
||||
return False
|
||||
|
||||
ip = request.headers.get('X-Real-IP') or request.remote_addr
|
||||
key = request.values.get('_key')
|
||||
secret = request.values.get('_secret')
|
||||
|
1
cmdb-api/api/lib/perm/authentication/__init__.py
Normal file
1
cmdb-api/api/lib/perm/authentication/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding:utf-8 -*-
|
@@ -15,7 +15,7 @@ try:
|
||||
except ImportError:
|
||||
from flask import _request_ctx_stack as stack
|
||||
|
||||
from api.flask_cas import routing
|
||||
from . import routing
|
||||
|
||||
|
||||
class CAS(object):
|
@@ -119,4 +119,4 @@ def create_cas_validate_url(cas_url, cas_route, service, ticket,
|
||||
('service', service),
|
||||
('ticket', ticket),
|
||||
('renew', renew),
|
||||
)
|
||||
)
|
@@ -1,14 +1,24 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
import bs4
|
||||
from flask import Blueprint
|
||||
from flask import current_app, session, request, url_for, redirect
|
||||
from flask_login import login_user, logout_user
|
||||
from flask import current_app
|
||||
from flask import redirect
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import url_for
|
||||
from flask_login import login_user
|
||||
from flask_login import logout_user
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from six.moves.urllib_request import urlopen
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import AuthenticateType
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
from .cas_urls import create_cas_login_url
|
||||
from .cas_urls import create_cas_logout_url
|
||||
from .cas_urls import create_cas_validate_url
|
||||
@@ -16,6 +26,7 @@ from .cas_urls import create_cas_validate_url
|
||||
blueprint = Blueprint('cas', __name__)
|
||||
|
||||
|
||||
@blueprint.route('/api/cas/login')
|
||||
@blueprint.route('/api/sso/login')
|
||||
def login():
|
||||
"""
|
||||
@@ -29,16 +40,20 @@ def login():
|
||||
If validation was successful the logged in username is saved in
|
||||
the user's session under the key `CAS_USERNAME_SESSION_KEY`.
|
||||
"""
|
||||
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
|
||||
|
||||
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
|
||||
if request.values.get("next"):
|
||||
session["next"] = request.values.get("next")
|
||||
|
||||
_service = url_for('cas.login', _external=True, next=session["next"]) \
|
||||
if session.get("next") else url_for('cas.login', _external=True)
|
||||
# _service = url_for('cas.login', _external=True)
|
||||
_service = "{}://{}{}".format(urlparse(request.referrer).scheme,
|
||||
urlparse(request.referrer).netloc,
|
||||
url_for('cas.login'))
|
||||
|
||||
redirect_url = create_cas_login_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGIN_ROUTE'],
|
||||
config['cas_server'],
|
||||
config['cas_login_route'],
|
||||
_service)
|
||||
|
||||
if 'ticket' in request.args:
|
||||
@@ -47,30 +62,38 @@ def login():
|
||||
if request.args.get('ticket'):
|
||||
|
||||
if validate(request.args['ticket']):
|
||||
redirect_url = session.get("next") or \
|
||||
current_app.config.get("CAS_AFTER_LOGIN")
|
||||
redirect_url = session.get("next") or config.get("cas_after_login") or "/"
|
||||
username = session.get("CAS_USERNAME")
|
||||
user = UserCache.get(username)
|
||||
login_user(user)
|
||||
|
||||
session.permanent = True
|
||||
|
||||
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
|
||||
else:
|
||||
del session[cas_token_session_key]
|
||||
redirect_url = create_cas_login_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGIN_ROUTE'],
|
||||
config['cas_server'],
|
||||
config['cas_login_route'],
|
||||
url_for('cas.login', _external=True),
|
||||
renew=True)
|
||||
|
||||
AuditCRUD.add_login_log(session.get("CAS_USERNAME"), False, ErrFormat.invalid_password)
|
||||
|
||||
current_app.logger.info("redirect to: {0}".format(redirect_url))
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
||||
@blueprint.route('/api/cas/logout')
|
||||
@blueprint.route('/api/sso/logout')
|
||||
def logout():
|
||||
"""
|
||||
When the user accesses this route they are logged out.
|
||||
"""
|
||||
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
|
||||
current_app.logger.info(config)
|
||||
|
||||
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
|
||||
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
|
||||
@@ -82,12 +105,14 @@ def logout():
|
||||
"next" in session and session.pop("next")
|
||||
|
||||
redirect_url = create_cas_logout_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGOUT_ROUTE'],
|
||||
config['cas_server'],
|
||||
config['cas_logout_route'],
|
||||
url_for('cas.login', _external=True, next=request.referrer))
|
||||
|
||||
logout_user()
|
||||
|
||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
||||
|
||||
current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
|
||||
|
||||
return redirect(redirect_url)
|
||||
@@ -100,14 +125,15 @@ def validate(ticket):
|
||||
and the validated username is saved in the session under the
|
||||
key `CAS_USERNAME_SESSION_KEY`.
|
||||
"""
|
||||
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
|
||||
|
||||
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
|
||||
|
||||
current_app.logger.debug("validating token {0}".format(ticket))
|
||||
|
||||
cas_validate_url = create_cas_validate_url(
|
||||
current_app.config['CAS_VALIDATE_SERVER'],
|
||||
current_app.config['CAS_VALIDATE_ROUTE'],
|
||||
config['cas_validate_server'],
|
||||
config['cas_validate_route'],
|
||||
url_for('cas.login', _external=True),
|
||||
ticket)
|
||||
|
||||
@@ -115,23 +141,35 @@ def validate(ticket):
|
||||
|
||||
try:
|
||||
response = urlopen(cas_validate_url).read()
|
||||
ticketid = _parse_tag(response, "cas:user")
|
||||
strs = [s.strip() for s in ticketid.split('|') if s.strip()]
|
||||
ticket_id = _parse_tag(response, "cas:user")
|
||||
strs = [s.strip() for s in ticket_id.split('|') if s.strip()]
|
||||
username, is_valid = None, False
|
||||
if len(strs) == 1:
|
||||
username = strs[0]
|
||||
is_valid = True
|
||||
user_info = json.loads(_parse_tag(response, "cas:other"))
|
||||
current_app.logger.info(user_info)
|
||||
except ValueError:
|
||||
current_app.logger.error("CAS returned unexpected result")
|
||||
is_valid = False
|
||||
return is_valid
|
||||
|
||||
if is_valid:
|
||||
current_app.logger.debug("valid")
|
||||
current_app.logger.debug("{}: {}".format(cas_username_session_key, username))
|
||||
session[cas_username_session_key] = username
|
||||
user = UserCache.get(username)
|
||||
if user is None:
|
||||
current_app.logger.info("create user: {}".format(username))
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
soup = bs4.BeautifulSoup(response)
|
||||
cas_user_map = config.get('cas_user_map')
|
||||
user_dict = dict()
|
||||
for k in cas_user_map:
|
||||
v = soup.find(cas_user_map[k]['tag'], cas_user_map[k].get('attrs', {}))
|
||||
user_dict[k] = v and v.text or None
|
||||
user_dict['password'] = uuid.uuid4().hex
|
||||
if "email" not in user_dict:
|
||||
user_dict['email'] = username
|
||||
|
||||
UserCRUD.add(**user_dict)
|
||||
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
user_info = ACLManager.get_user_info(username)
|
||||
@@ -164,4 +202,5 @@ def _parse_tag(string, tag):
|
||||
|
||||
if soup.find(tag) is None:
|
||||
return ''
|
||||
|
||||
return soup.find(tag).string.strip()
|
67
cmdb-api/api/lib/perm/authentication/ldap.py
Normal file
67
cmdb-api/api/lib/perm/authentication/ldap.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import uuid
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from ldap3 import ALL
|
||||
from ldap3 import AUTO_BIND_NO_TLS
|
||||
from ldap3 import Connection
|
||||
from ldap3 import Server
|
||||
from ldap3.core.exceptions import LDAPBindError
|
||||
from ldap3.core.exceptions import LDAPCertificateError
|
||||
from ldap3.core.exceptions import LDAPSocketOpenError
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import AuthenticateType
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
from api.models.acl import User
|
||||
|
||||
|
||||
def authenticate_with_ldap(username, password):
|
||||
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
||||
|
||||
server = Server(config.get('ldap_server'), get_info=ALL, connect_timeout=3)
|
||||
if '@' in username:
|
||||
email = username
|
||||
who = config.get('ldap_user_dn').format(username.split('@')[0])
|
||||
else:
|
||||
who = config.get('ldap_user_dn').format(username)
|
||||
email = "{}@{}".format(who, config.get('ldap_domain'))
|
||||
|
||||
username = username.split('@')[0]
|
||||
user = User.query.get_by_username(username)
|
||||
try:
|
||||
if not password:
|
||||
raise LDAPCertificateError
|
||||
|
||||
try:
|
||||
conn = Connection(server, user=who, password=password, auto_bind=AUTO_BIND_NO_TLS)
|
||||
except LDAPBindError:
|
||||
conn = Connection(server,
|
||||
user=f"{username}@{config.get('ldap_domain')}",
|
||||
password=password,
|
||||
auto_bind=AUTO_BIND_NO_TLS)
|
||||
|
||||
if conn.result['result'] != 0:
|
||||
AuditCRUD.add_login_log(username, False, ErrFormat.invalid_password)
|
||||
raise LDAPBindError
|
||||
else:
|
||||
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
|
||||
if not user:
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
user = UserCRUD.add(username=username, email=email, password=uuid.uuid4().hex)
|
||||
|
||||
return user, True
|
||||
|
||||
except LDAPBindError as e:
|
||||
current_app.logger.info(e)
|
||||
return user, False
|
||||
|
||||
except LDAPSocketOpenError as e:
|
||||
current_app.logger.info(e)
|
||||
return abort(403, ErrFormat.ldap_connection_failed)
|
30
cmdb-api/api/lib/perm/authentication/oauth2/__init__.py
Normal file
30
cmdb-api/api/lib/perm/authentication/oauth2/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from . import routing
|
||||
|
||||
|
||||
class OAuth2(object):
|
||||
def __init__(self, app=None, url_prefix=None):
|
||||
self._app = app
|
||||
if app is not None:
|
||||
self.init_app(app, url_prefix)
|
||||
|
||||
@staticmethod
|
||||
def init_app(app, url_prefix=None):
|
||||
# Configuration defaults
|
||||
app.config.setdefault('OAUTH2_GRANT_TYPE', 'authorization_code')
|
||||
app.config.setdefault('OAUTH2_RESPONSE_TYPE', 'code')
|
||||
app.config.setdefault('OAUTH2_AFTER_LOGIN', '/')
|
||||
|
||||
app.config.setdefault('OIDC_GRANT_TYPE', 'authorization_code')
|
||||
app.config.setdefault('OIDC_RESPONSE_TYPE', 'code')
|
||||
app.config.setdefault('OIDC_AFTER_LOGIN', '/')
|
||||
|
||||
# Register Blueprint
|
||||
app.register_blueprint(routing.blueprint, url_prefix=url_prefix)
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return self._app or current_app
|
139
cmdb-api/api/lib/perm/authentication/oauth2/routing.py
Normal file
139
cmdb-api/api/lib/perm/authentication/oauth2/routing.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import secrets
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
from flask import Blueprint
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import redirect
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import url_for
|
||||
from flask_login import login_user
|
||||
from flask_login import logout_user
|
||||
from six.moves.urllib.parse import urlencode
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
||||
blueprint = Blueprint('oauth2', __name__)
|
||||
|
||||
|
||||
@blueprint.route('/api/<string:auth_type>/login')
|
||||
def login(auth_type):
|
||||
config = AuthenticateDataCRUD(auth_type.upper()).get()
|
||||
|
||||
if request.values.get("next"):
|
||||
session["next"] = request.values.get("next")
|
||||
|
||||
session[f'{auth_type}_state'] = secrets.token_urlsafe(16)
|
||||
|
||||
auth_type = auth_type.upper()
|
||||
|
||||
redirect_uri = "{}://{}{}".format(urlparse(request.referrer).scheme,
|
||||
urlparse(request.referrer).netloc,
|
||||
url_for('oauth2.callback', auth_type=auth_type.lower()))
|
||||
qs = urlencode({
|
||||
'client_id': config['client_id'],
|
||||
'redirect_uri': redirect_uri,
|
||||
'response_type': current_app.config[f'{auth_type}_RESPONSE_TYPE'],
|
||||
'scope': ' '.join(config['scopes'] or []),
|
||||
'state': session[f'{auth_type.lower()}_state'],
|
||||
})
|
||||
|
||||
return redirect("{}?{}".format(config['authorize_url'].split('?')[0], qs))
|
||||
|
||||
|
||||
@blueprint.route('/api/<string:auth_type>/callback')
|
||||
def callback(auth_type):
|
||||
auth_type = auth_type.upper()
|
||||
config = AuthenticateDataCRUD(auth_type).get()
|
||||
|
||||
redirect_url = session.get("next") or config.get('after_login') or '/'
|
||||
|
||||
if request.values['state'] != session.get(f'{auth_type.lower()}_state'):
|
||||
return abort(401, "state is invalid")
|
||||
|
||||
if 'code' not in request.values:
|
||||
return abort(401, 'code is invalid')
|
||||
|
||||
response = requests.post(config['token_url'], data={
|
||||
'client_id': config['client_id'],
|
||||
'client_secret': config['client_secret'],
|
||||
'code': request.values['code'],
|
||||
'grant_type': current_app.config[f'{auth_type}_GRANT_TYPE'],
|
||||
'redirect_uri': url_for('oauth2.callback', auth_type=auth_type.lower(), _external=True),
|
||||
}, headers={'Accept': 'application/json'})
|
||||
if response.status_code != 200:
|
||||
current_app.logger.error(response.text)
|
||||
return abort(401)
|
||||
access_token = response.json().get('access_token')
|
||||
if not access_token:
|
||||
return abort(401)
|
||||
|
||||
response = requests.get(config['user_info']['url'], headers={
|
||||
'Authorization': 'Bearer {}'.format(access_token),
|
||||
'Accept': 'application/json',
|
||||
})
|
||||
if response.status_code != 200:
|
||||
return abort(401)
|
||||
|
||||
res = response.json()
|
||||
email = res.get(config['user_info']['email'])
|
||||
username = res.get(config['user_info']['username'])
|
||||
avatar = res.get(config['user_info'].get('avatar'))
|
||||
user = UserCache.get(username)
|
||||
if user is None:
|
||||
current_app.logger.info("create user: {}".format(username))
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
user_dict = dict(username=username, email=email, avatar=avatar)
|
||||
user_dict['password'] = uuid.uuid4().hex
|
||||
|
||||
user = UserCRUD.add(**user_dict)
|
||||
|
||||
# log the user in
|
||||
login_user(user)
|
||||
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
user_info = ACLManager.get_user_info(username)
|
||||
|
||||
session["acl"] = dict(uid=user_info.get("uid"),
|
||||
avatar=user.avatar if user else user_info.get("avatar"),
|
||||
userId=user_info.get("uid"),
|
||||
rid=user_info.get("rid"),
|
||||
userName=user_info.get("username"),
|
||||
nickName=user_info.get("nickname") or user_info.get("username"),
|
||||
parentRoles=user_info.get("parents"),
|
||||
childRoles=user_info.get("children"),
|
||||
roleName=user_info.get("role"))
|
||||
session["uid"] = user_info.get("uid")
|
||||
|
||||
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
||||
@blueprint.route('/api/<string:auth_type>/logout')
|
||||
def logout(auth_type):
|
||||
"acl" in session and session.pop("acl")
|
||||
"uid" in session and session.pop("uid")
|
||||
f'{auth_type}_state' in session and session.pop(f'{auth_type}_state')
|
||||
"next" in session and session.pop("next")
|
||||
|
||||
redirect_url = url_for('oauth2.login', auth_type=auth_type, _external=True, next=request.referrer)
|
||||
|
||||
logout_user()
|
||||
|
||||
current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
|
||||
|
||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
||||
|
||||
return redirect(redirect_url)
|
@@ -1,29 +1,34 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
|
||||
class CommonErrFormat(object):
|
||||
unauthorized = "未认证"
|
||||
unknown_error = "未知错误"
|
||||
unauthorized = _l("unauthorized") # 未认证
|
||||
unknown_error = _l("unknown error") # 未知错误
|
||||
|
||||
invalid_request = "不合法的请求"
|
||||
invalid_operation = "无效的操作"
|
||||
invalid_request = _l("Illegal request") # 不合法的请求
|
||||
invalid_operation = _l("Invalid operation") # 无效的操作
|
||||
|
||||
not_found = "不存在"
|
||||
not_found = _l("does not exist") # 不存在
|
||||
|
||||
circular_dependency_error = "存在循环依赖!"
|
||||
circular_dependency_error = _l("There is a circular dependency!") # 存在循环依赖!
|
||||
|
||||
unknown_search_error = "未知搜索错误"
|
||||
unknown_search_error = _l("Unknown search error") # 未知搜索错误
|
||||
|
||||
invalid_json = "json格式似乎不正确了, 请仔细确认一下!"
|
||||
# json格式似乎不正确了, 请仔细确认一下!
|
||||
invalid_json = _l("The json format seems to be incorrect, please confirm carefully!")
|
||||
|
||||
datetime_argument_invalid = "参数 {} 格式不正确, 格式必须是: yyyy-mm-dd HH:MM:SS"
|
||||
# 参数 {} 格式不正确, 格式必须是: yyyy-mm-dd HH:MM:SS
|
||||
datetime_argument_invalid = _l("The format of parameter {} is incorrect, the format must be: yyyy-mm-dd HH:MM:SS")
|
||||
|
||||
argument_value_required = "参数 {} 的值不能为空!"
|
||||
argument_required = "请求缺少参数 {}"
|
||||
argument_invalid = "参数 {} 的值无效"
|
||||
argument_str_length_limit = "参数 {} 的长度必须 <= {}"
|
||||
argument_value_required = _l("The value of parameter {} cannot be empty!") # 参数 {} 的值不能为空!
|
||||
argument_required = _l("The request is missing parameters {}") # 请求缺少参数 {}
|
||||
argument_invalid = _l("Invalid value for parameter {}") # 参数 {} 的值无效
|
||||
argument_str_length_limit = _l("The length of parameter {} must be <= {}") # 参数 {} 的长度必须 <= {}
|
||||
|
||||
role_required = "角色 {} 才能操作!"
|
||||
user_not_found = "用户 {} 不存在"
|
||||
no_permission = "您没有资源: {} 的{}权限!"
|
||||
no_permission2 = "您没有操作权限!"
|
||||
no_permission_only_owner = "只有创建人或者管理员才有权限!"
|
||||
role_required = _l("Role {} can only operate!") # 角色 {} 才能操作!
|
||||
user_not_found = _l("User {} does not exist") # 用户 {} 不存在
|
||||
no_permission = _l("You do not have {} permission for resource: {}!") # 您没有资源: {} 的{}权限!
|
||||
no_permission2 = _l("You do not have permission to operate!") # 您没有操作权限!
|
||||
no_permission_only_owner = _l("Only the creator or administrator has permission!") # 只有创建人或者管理员才有权限!
|
||||
|
@@ -38,7 +38,6 @@ def string_to_bytes(value):
|
||||
byte_string = value
|
||||
else:
|
||||
byte_string = value.encode("utf-8")
|
||||
|
||||
return byte_string
|
||||
|
||||
|
||||
@@ -314,7 +313,7 @@ class KeyManage:
|
||||
secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||
if not ok:
|
||||
return {"message": msg, "status": "failed"}
|
||||
return true
|
||||
status = self.backend.get(backend_seal_key)
|
||||
return status == "block"
|
||||
|
||||
|
@@ -5,17 +5,18 @@ import copy
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
||||
from ldap3 import Server, Connection, ALL
|
||||
from ldap3.core.exceptions import LDAPBindError, LDAPCertificateError
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from flask_sqlalchemy import BaseQuery
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.database import CRUDModel
|
||||
from api.lib.database import Model
|
||||
from api.lib.database import Model2
|
||||
from api.lib.database import SoftDeleteMixin
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.const import OperateType
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
||||
|
||||
class App(Model):
|
||||
@@ -28,21 +29,26 @@ class App(Model):
|
||||
|
||||
|
||||
class UserQuery(BaseQuery):
|
||||
def _join(self, *args, **kwargs):
|
||||
super(UserQuery, self)._join(*args, **kwargs)
|
||||
|
||||
def authenticate(self, login, password):
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
|
||||
user = self.filter(db.or_(User.username == login,
|
||||
User.email == login)).filter(User.deleted.is_(False)).filter(User.block == 0).first()
|
||||
if user:
|
||||
current_app.logger.info(user)
|
||||
authenticated = user.check_password(password)
|
||||
if authenticated:
|
||||
from api.tasks.acl import op_record
|
||||
op_record.apply_async(args=(None, login, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
|
||||
_id = AuditCRUD.add_login_log(login, True, ErrFormat.login_succeed)
|
||||
session['LOGIN_ID'] = _id
|
||||
else:
|
||||
AuditCRUD.add_login_log(login, False, ErrFormat.invalid_password)
|
||||
else:
|
||||
authenticated = False
|
||||
|
||||
AuditCRUD.add_login_log(login, False, ErrFormat.user_not_found.format(login))
|
||||
|
||||
current_app.logger.info(("login", login, user, authenticated))
|
||||
|
||||
return user, authenticated
|
||||
|
||||
def authenticate_with_key(self, key, secret, args, path):
|
||||
@@ -57,38 +63,6 @@ class UserQuery(BaseQuery):
|
||||
|
||||
return user, authenticated
|
||||
|
||||
def authenticate_with_ldap(self, username, password):
|
||||
server = Server(current_app.config.get('LDAP_SERVER'), get_info=ALL)
|
||||
if '@' in username:
|
||||
email = username
|
||||
who = current_app.config.get('LDAP_USER_DN').format(username.split('@')[0])
|
||||
else:
|
||||
who = current_app.config.get('LDAP_USER_DN').format(username)
|
||||
email = "{}@{}".format(who, current_app.config.get('LDAP_DOMAIN'))
|
||||
|
||||
username = username.split('@')[0]
|
||||
user = self.get_by_username(username)
|
||||
try:
|
||||
if not password:
|
||||
raise LDAPCertificateError
|
||||
|
||||
conn = Connection(server, user=who, password=password)
|
||||
conn.bind()
|
||||
if conn.result['result'] != 0:
|
||||
raise LDAPBindError
|
||||
conn.unbind()
|
||||
|
||||
if not user:
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
user = UserCRUD.add(username=username, email=email)
|
||||
|
||||
from api.tasks.acl import op_record
|
||||
op_record.apply_async(args=(None, username, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
|
||||
|
||||
return user, True
|
||||
except LDAPBindError:
|
||||
return user, False
|
||||
|
||||
def search(self, key):
|
||||
query = self.filter(db.or_(User.email == key,
|
||||
User.nickname.ilike('%' + key + '%'),
|
||||
@@ -138,6 +112,7 @@ class User(CRUDModel, SoftDeleteMixin):
|
||||
wx_id = db.Column(db.String(32))
|
||||
employee_id = db.Column(db.String(16), index=True)
|
||||
avatar = db.Column(db.String(128))
|
||||
|
||||
# apps = db.Column(db.JSON)
|
||||
|
||||
def __str__(self):
|
||||
@@ -168,11 +143,9 @@ class User(CRUDModel, SoftDeleteMixin):
|
||||
|
||||
|
||||
class RoleQuery(BaseQuery):
|
||||
def _join(self, *args, **kwargs):
|
||||
super(RoleQuery, self)._join(*args, **kwargs)
|
||||
|
||||
def authenticate(self, login, password):
|
||||
role = self.filter(Role.name == login).first()
|
||||
role = self.filter(Role.name == login).filter(Role.deleted.is_(False)).first()
|
||||
if role:
|
||||
authenticated = role.check_password(password)
|
||||
|
||||
@@ -377,3 +350,16 @@ class AuditTriggerLog(Model):
|
||||
current = db.Column(db.JSON, default=dict(), comment='当前数据')
|
||||
extra = db.Column(db.JSON, default=dict(), comment='权限名')
|
||||
source = db.Column(db.String(16), default='', comment='来源')
|
||||
|
||||
|
||||
class AuditLoginLog(Model2):
|
||||
__tablename__ = "acl_audit_login_logs"
|
||||
|
||||
username = db.Column(db.String(64), index=True)
|
||||
channel = db.Column(db.Enum('web', 'api'), default="web")
|
||||
ip = db.Column(db.String(15))
|
||||
browser = db.Column(db.String(256))
|
||||
description = db.Column(db.String(128))
|
||||
is_ok = db.Column(db.Boolean)
|
||||
login_at = db.Column(db.DateTime)
|
||||
logout_at = db.Column(db.DateTime)
|
||||
|
@@ -218,6 +218,8 @@ class CIRelation(Model):
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
|
||||
|
||||
ancestor_ids = db.Column(db.String(128), index=True)
|
||||
|
||||
first_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.first_ci_id")
|
||||
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_id")
|
||||
relation_type = db.relationship("RelationType", backref="c_ci_relations.relation_type_id")
|
||||
|
@@ -96,3 +96,11 @@ class NoticeConfig(Model):
|
||||
|
||||
platform = db.Column(db.VARCHAR(255), nullable=False)
|
||||
info = db.Column(db.JSON)
|
||||
|
||||
|
||||
class CommonFile(Model):
|
||||
__tablename__ = 'common_file'
|
||||
|
||||
file_name = db.Column(db.VARCHAR(512), nullable=False, index=True)
|
||||
origin_name = db.Column(db.VARCHAR(512), nullable=False)
|
||||
binary = db.Column(db.LargeBinary(16777216), nullable=False)
|
||||
|
@@ -25,10 +25,9 @@ from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
||||
|
||||
@celery.task(base=QueueOnce,
|
||||
name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,
|
||||
once={"graceful": True, "unlock_before_run": True})
|
||||
@celery.task(name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def role_rebuild(rids, app_id):
|
||||
rids = rids if isinstance(rids, list) else [rids]
|
||||
@@ -190,18 +189,18 @@ def cancel_trigger(_id, resource_id=None, operator_uid=None):
|
||||
|
||||
@celery.task(name="acl.op_record", queue=ACL_QUEUE)
|
||||
@reconnect_db
|
||||
def op_record(app, rolename, operate_type, obj):
|
||||
def op_record(app, role_name, operate_type, obj):
|
||||
if isinstance(app, int):
|
||||
app = AppCache.get(app)
|
||||
app = app and app.name
|
||||
|
||||
if isinstance(rolename, int):
|
||||
u = UserCache.get(rolename)
|
||||
if isinstance(role_name, int):
|
||||
u = UserCache.get(role_name)
|
||||
if u:
|
||||
rolename = u.username
|
||||
role_name = u.username
|
||||
if not u:
|
||||
r = RoleCache.get(rolename)
|
||||
r = RoleCache.get(role_name)
|
||||
if r:
|
||||
rolename = r.name
|
||||
role_name = r.name
|
||||
|
||||
OperateRecordCRUD.add(app, rolename, operate_type, obj)
|
||||
OperateRecordCRUD.add(app, role_name, operate_type, obj)
|
||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -97,16 +98,30 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id):
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if cr and str(cr.second_ci_id) not in grandson:
|
||||
grandson[str(cr.second_ci_id)] = cr.second_ci.type_id
|
||||
|
||||
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
|
||||
|
||||
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||
|
||||
@@ -156,20 +171,31 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
try:
|
||||
db.session.commit()
|
||||
except:
|
||||
pass
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id):
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
|
||||
if str(child_id) in grandson:
|
||||
grandson.pop(str(child_id))
|
||||
|
||||
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
|
||||
|
||||
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||
|
||||
|
@@ -1,24 +1,24 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import requests
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.const import COMMON_SETTING_QUEUE
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import Department
|
||||
from api.models.common_setting import Department, Employee
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
|
||||
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=COMMON_SETTING_QUEUE)
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
"""
|
||||
:param e_list:{acl_rid: 11, department_id: 22}
|
||||
:param new_d_id
|
||||
:param op_uid
|
||||
"""
|
||||
db.session.remove()
|
||||
|
||||
result = []
|
||||
new_department = Department.get_by(
|
||||
first=True, department_id=new_d_id, to_dict=False)
|
||||
@@ -75,3 +75,41 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
result.append(ErrFormat.acl_add_user_to_role_failed.format(str(e)))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@celery.task(name="common_setting.refresh_employee_acl_info", queue=CMDB_QUEUE)
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def refresh_employee_acl_info():
|
||||
acl = ACLManager('acl')
|
||||
role_map = {role['name']: role for role in acl.get_all_roles()}
|
||||
|
||||
criterion = [
|
||||
Employee.deleted == 0
|
||||
]
|
||||
query = Employee.query.filter(*criterion).order_by(
|
||||
Employee.created_at.desc()
|
||||
)
|
||||
|
||||
for em in query.all():
|
||||
if em.acl_uid and em.acl_rid:
|
||||
continue
|
||||
role = role_map.get(em.username, None)
|
||||
if not role:
|
||||
continue
|
||||
|
||||
params = dict()
|
||||
if not em.acl_uid:
|
||||
params['acl_uid'] = role.get('uid', 0)
|
||||
|
||||
if not em.acl_rid:
|
||||
params['acl_rid'] = role.get('id', 0)
|
||||
|
||||
try:
|
||||
em.update(**params)
|
||||
current_app.logger.info(
|
||||
f"refresh_employee_acl_info success, employee_id: {em.employee_id}, uid: {em.acl_uid}, "
|
||||
f"rid: {em.acl_rid}")
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
continue
|
||||
|
BIN
cmdb-api/api/translations/zh/LC_MESSAGES/messages.mo
Normal file
BIN
cmdb-api/api/translations/zh/LC_MESSAGES/messages.mo
Normal file
Binary file not shown.
819
cmdb-api/api/translations/zh/LC_MESSAGES/messages.po
Normal file
819
cmdb-api/api/translations/zh/LC_MESSAGES/messages.po
Normal file
@@ -0,0 +1,819 @@
|
||||
# Chinese translations for PROJECT.
|
||||
# Copyright (C) 2023 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-01-03 11:39+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
"Language-Team: zh <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.14.0\n"
|
||||
|
||||
#: api/lib/resp_format.py:7
|
||||
msgid "unauthorized"
|
||||
msgstr "未认证"
|
||||
|
||||
#: api/lib/resp_format.py:8
|
||||
msgid "unknown error"
|
||||
msgstr "未知错误"
|
||||
|
||||
#: api/lib/resp_format.py:10
|
||||
msgid "Illegal request"
|
||||
msgstr "不合法的请求"
|
||||
|
||||
#: api/lib/resp_format.py:11
|
||||
msgid "Invalid operation"
|
||||
msgstr "无效的操作"
|
||||
|
||||
#: api/lib/resp_format.py:13
|
||||
msgid "does not exist"
|
||||
msgstr "不存在"
|
||||
|
||||
#: api/lib/resp_format.py:15
|
||||
msgid "There is a circular dependency!"
|
||||
msgstr "存在循环依赖!"
|
||||
|
||||
#: api/lib/resp_format.py:17
|
||||
msgid "Unknown search error"
|
||||
msgstr "未知搜索错误"
|
||||
|
||||
#: api/lib/resp_format.py:20
|
||||
msgid "The json format seems to be incorrect, please confirm carefully!"
|
||||
msgstr "# json格式似乎不正确了, 请仔细确认一下!"
|
||||
|
||||
#: api/lib/resp_format.py:23
|
||||
msgid ""
|
||||
"The format of parameter {} is incorrect, the format must be: yyyy-mm-dd "
|
||||
"HH:MM:SS"
|
||||
msgstr "参数 {} 格式不正确, 格式必须是: yyyy-mm-dd HH:MM:SS"
|
||||
|
||||
#: api/lib/resp_format.py:25
|
||||
msgid "The value of parameter {} cannot be empty!"
|
||||
msgstr "参数 {} 的值不能为空!"
|
||||
|
||||
#: api/lib/resp_format.py:26
|
||||
msgid "The request is missing parameters {}"
|
||||
msgstr "请求缺少参数 {}"
|
||||
|
||||
#: api/lib/resp_format.py:27
|
||||
msgid "Invalid value for parameter {}"
|
||||
msgstr "参数 {} 的值无效"
|
||||
|
||||
#: api/lib/resp_format.py:28
|
||||
msgid "The length of parameter {} must be <= {}"
|
||||
msgstr "参数 {} 的长度必须 <= {}"
|
||||
|
||||
#: api/lib/resp_format.py:30
|
||||
msgid "Role {} can only operate!"
|
||||
msgstr "角色 {} 才能操作!"
|
||||
|
||||
#: api/lib/resp_format.py:31
|
||||
msgid "User {} does not exist"
|
||||
msgstr "用户 {} 不存在"
|
||||
|
||||
#: api/lib/resp_format.py:32
|
||||
msgid "You do not have {} permission for resource: {}!"
|
||||
msgstr "您没有资源: {} 的{}权限!"
|
||||
|
||||
#: api/lib/resp_format.py:33
|
||||
msgid "You do not have permission to operate!"
|
||||
msgstr "您没有操作权限!"
|
||||
|
||||
#: api/lib/resp_format.py:34
|
||||
msgid "Only the creator or administrator has permission!"
|
||||
msgstr "只有创建人或者管理员才有权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:9
|
||||
msgid "CI Model"
|
||||
msgstr "模型配置"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:11
|
||||
msgid "Invalid relation type: {}"
|
||||
msgstr "无效的关系类型: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:12
|
||||
msgid "CIType is not found"
|
||||
msgstr "模型不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:15
|
||||
msgid "The type of parameter attributes must be a list"
|
||||
msgstr "参数 attributes 类型必须是列表"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:16
|
||||
msgid "The file doesn't seem to be uploaded"
|
||||
msgstr "文件似乎并未上传"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:18
|
||||
msgid "Attribute {} does not exist!"
|
||||
msgstr "属性 {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:19
|
||||
msgid ""
|
||||
"This attribute is the unique identifier of the model and cannot be "
|
||||
"deleted!"
|
||||
msgstr "该属性是模型的唯一标识,不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:21
|
||||
msgid "This attribute is referenced by model {} and cannot be deleted!"
|
||||
msgstr "该属性被模型 {} 引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:23
|
||||
msgid "The value type of the attribute is not allowed to be modified!"
|
||||
msgstr "属性的值类型不允许修改!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:25
|
||||
msgid "Multiple values are not allowed to be modified!"
|
||||
msgstr "多值不被允许修改!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:27
|
||||
msgid "Modifying the index is not allowed for non-administrators!"
|
||||
msgstr "修改索引 非管理员不被允许!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:28
|
||||
msgid "Index switching failed!"
|
||||
msgstr "索引切换失败!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:29
|
||||
msgid "The predefined value is of the wrong type!"
|
||||
msgstr "预定义值的类型不对!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:30
|
||||
msgid "Duplicate attribute name {}"
|
||||
msgstr "重复的属性名 {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:31
|
||||
msgid "Failed to create attribute {}!"
|
||||
msgstr "创建属性 {} 失败!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:32
|
||||
msgid "Modify attribute {} failed!"
|
||||
msgstr "修改属性 {} 失败!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:33
|
||||
msgid "You do not have permission to modify this attribute!"
|
||||
msgstr "您没有权限修改该属性!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:34
|
||||
msgid "Only creators and administrators are allowed to delete attributes!"
|
||||
msgstr "目前只允许 属性创建人、管理员 删除属性!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:37
|
||||
msgid ""
|
||||
"Attribute field names cannot be built-in fields: id, _id, ci_id, type, "
|
||||
"_type, ci_type"
|
||||
msgstr "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:39
|
||||
msgid "Predefined value: Other model request parameters are illegal!"
|
||||
msgstr "预定义值: 其他模型请求参数不合法!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:42
|
||||
msgid "CI {} does not exist"
|
||||
msgstr "CI {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:43
|
||||
msgid "Multiple attribute joint unique verification failed: {}"
|
||||
msgstr "多属性联合唯一校验不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:44
|
||||
msgid "The model's primary key {} does not exist!"
|
||||
msgstr "模型的主键 {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:45
|
||||
msgid "Primary key {} is missing"
|
||||
msgstr "主键字段 {} 缺失"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:46
|
||||
msgid "CI already exists!"
|
||||
msgstr "CI 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:47
|
||||
msgid "Relationship constraint: {}, verification failed"
|
||||
msgstr "关系约束: {}, 校验失败"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:49
|
||||
msgid ""
|
||||
"Many-to-many relationship constraint: Model {} <-> {} already has a many-"
|
||||
"to-many relationship!"
|
||||
msgstr "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:52
|
||||
msgid "CI relationship: {} does not exist"
|
||||
msgstr "CI关系: {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:55
|
||||
msgid "In search expressions, not supported before parentheses: or, not"
|
||||
msgstr "搜索表达式里小括号前不支持: 或、非"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:57
|
||||
msgid "Model {} does not exist"
|
||||
msgstr "模型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:58
|
||||
msgid "Model {} already exists"
|
||||
msgstr "模型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:59
|
||||
msgid "The primary key is undefined or has been deleted"
|
||||
msgstr "主键未定义或者已被删除"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:60
|
||||
msgid "Only the creator can delete it!"
|
||||
msgstr "只有创建人才能删除它!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:61
|
||||
msgid "The model cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:65
|
||||
msgid ""
|
||||
"The model cannot be deleted because the model is referenced by the "
|
||||
"relational view {}"
|
||||
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
msgid "Model group {} does not exist"
|
||||
msgstr "模型分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:68
|
||||
msgid "Model group {} already exists"
|
||||
msgstr "模型分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
msgid "Model relationship {} does not exist"
|
||||
msgstr "模型关系 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
msgid "Attribute group {} already exists"
|
||||
msgstr "属性分组 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
msgid "Attribute group {} does not exist"
|
||||
msgstr "属性分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
||||
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:74
|
||||
msgid "The unique constraint already exists!"
|
||||
msgstr "唯一约束已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
|
||||
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:77
|
||||
msgid "Duplicated trigger"
|
||||
msgstr "重复的触发器"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:92
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:107
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||
msgstr "自动发现规则的应用不能重复定义!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:112
|
||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
msgid "The unique_key method must return a non-empty string!"
|
||||
msgstr "unique_key方法必须返回非空字符串!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:123
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
msgid "The list returned by the attributes method cannot be empty!"
|
||||
msgstr "attributes方法返回的list不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:128
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:131
|
||||
msgid "CI filter authorization is currently not supported or query"
|
||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:134
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
msgid "You do not have permission to operate this CI!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:138
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:8
|
||||
msgid "Company info already existed"
|
||||
msgstr "公司信息已存在,无法创建!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:10
|
||||
msgid "No file part"
|
||||
msgstr "没有文件部分"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:11
|
||||
msgid "File is required"
|
||||
msgstr "文件是必须的"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:12
|
||||
msgid "File not found"
|
||||
msgstr "文件不存在!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:13
|
||||
msgid "File type not allowed"
|
||||
msgstr "文件类型不允许!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:14
|
||||
msgid "Upload failed: {}"
|
||||
msgstr "上传失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:16
|
||||
msgid "Direct supervisor is not self"
|
||||
msgstr "直属上级不能是自己"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:17
|
||||
msgid "Parent department is not self"
|
||||
msgstr "上级部门不能是自己"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:18
|
||||
msgid "Employee list is empty"
|
||||
msgstr "员工列表为空"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:20
|
||||
msgid "Column name not support"
|
||||
msgstr "不支持的列名"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:21
|
||||
msgid "Password is required"
|
||||
msgstr "密码是必须的"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:22
|
||||
msgid "Employee acl rid is zero"
|
||||
msgstr "员工ACL角色ID不能为0"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:24
|
||||
msgid "Generate excel failed: {}"
|
||||
msgstr "生成excel失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:25
|
||||
msgid "Rename columns failed: {}"
|
||||
msgstr "重命名字段失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:26
|
||||
msgid "Cannot block this employee is other direct supervisor"
|
||||
msgstr "该员工是其他员工的直属上级, 不能禁用"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:28
|
||||
msgid "Cannot block this employee is department manager"
|
||||
msgstr "该员工是部门负责人, 不能禁用"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:30
|
||||
msgid "Employee id [{}] not found"
|
||||
msgstr "员工ID [{}] 不存在!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:31
|
||||
msgid "Value is required"
|
||||
msgstr "值是必须的"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:32
|
||||
msgid "Email already exists"
|
||||
msgstr "邮箱已存在!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:33
|
||||
msgid "Query {} none keep value empty"
|
||||
msgstr "查询 {} 空值时请保持value为空"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:34
|
||||
msgid "Not support operator: {}"
|
||||
msgstr "不支持的操作符: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:35
|
||||
msgid "Not support relation: {}"
|
||||
msgstr "不支持的关系: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:36
|
||||
msgid "Conditions field missing"
|
||||
msgstr " conditions内元素字段缺失,请检查!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:37
|
||||
msgid "Datetime format error: {}"
|
||||
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:38
|
||||
msgid "Department level relation error"
|
||||
msgstr "部门层级关系不正确"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:39
|
||||
msgid "Delete reserved department name"
|
||||
msgstr "保留部门,无法删除!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:40
|
||||
msgid "Department id is required"
|
||||
msgstr "部门ID是必须的"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:41
|
||||
msgid "Department list is required"
|
||||
msgstr "部门列表是必须的"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:42
|
||||
msgid "{} Cannot to be parent department"
|
||||
msgstr "{} 不能设置为上级部门"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:43
|
||||
msgid "Department id [{}] not found"
|
||||
msgstr "部门ID [{}] 不存在"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:44
|
||||
msgid "Parent department id must more than zero"
|
||||
msgstr "上级部门ID必须大于0"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:45
|
||||
msgid "Department name [{}] already exists"
|
||||
msgstr "部门名称 [{}] 已存在"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:46
|
||||
msgid "New department is none"
|
||||
msgstr "新部门是空的"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:48
|
||||
msgid "ACL edit user failed: {}"
|
||||
msgstr "ACL 修改用户失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:49
|
||||
msgid "ACL uid not found: {}"
|
||||
msgstr "ACL 用户UID [{}] 不存在"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:50
|
||||
msgid "ACL add user failed: {}"
|
||||
msgstr "ACL 添加用户失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:51
|
||||
msgid "ACL add role failed: {}"
|
||||
msgstr "ACL 添加角色失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:52
|
||||
msgid "ACL update role failed: {}"
|
||||
msgstr "ACL 更新角色失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:53
|
||||
msgid "ACL get all users failed: {}"
|
||||
msgstr "ACL 获取所有用户失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:54
|
||||
msgid "ACL remove user from role failed: {}"
|
||||
msgstr "ACL 从角色中移除用户失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:55
|
||||
msgid "ACL add user to role failed: {}"
|
||||
msgstr "ACL 添加用户到角色失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:56
|
||||
msgid "ACL import user failed: {}"
|
||||
msgstr "ACL 导入用户失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:58
|
||||
msgid "Nickname is required"
|
||||
msgstr "昵称不能为空"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:59
|
||||
msgid "Username is required"
|
||||
msgstr "用户名不能为空"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:60
|
||||
msgid "Email is required"
|
||||
msgstr "邮箱不能为空"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:61
|
||||
msgid "Email format error"
|
||||
msgstr "邮箱格式错误"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:62
|
||||
msgid "Email send timeout"
|
||||
msgstr "邮件发送超时"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:64
|
||||
msgid "Common data not found {} "
|
||||
msgstr "ID {} 找不到记录"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:65
|
||||
msgid "Common data {} already existed"
|
||||
msgstr "{} 已经存在"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:66
|
||||
msgid "Notice platform {} existed"
|
||||
msgstr "{} 已经存在"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:67
|
||||
msgid "Notice {} not existed"
|
||||
msgstr "{} 配置项不存在"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:68
|
||||
msgid "Notice please config messenger first"
|
||||
msgstr "请先配置messenger URL"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:69
|
||||
msgid "Notice bind err with empty mobile"
|
||||
msgstr "绑定错误,手机号为空"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:70
|
||||
msgid "Notice bind failed: {}"
|
||||
msgstr "绑定失败: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:71
|
||||
msgid "Notice bind success"
|
||||
msgstr "绑定成功"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:72
|
||||
msgid "Notice remove bind success"
|
||||
msgstr "解绑成功"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:74
|
||||
msgid "Not support test type: {}"
|
||||
msgstr "不支持的测试类型: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:75
|
||||
msgid "Not support auth type: {}"
|
||||
msgstr "不支持的认证类型: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:76
|
||||
msgid "LDAP server connect timeout"
|
||||
msgstr "LDAP服务器连接超时"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:77
|
||||
msgid "LDAP server connect not available"
|
||||
msgstr "LDAP服务器连接不可用"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:78
|
||||
msgid "LDAP test unknown error: {}"
|
||||
msgstr "LDAP测试未知错误: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:79
|
||||
msgid "Common data not support auth type: {}"
|
||||
msgstr "通用数据不支持auth类型: {}"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:80
|
||||
msgid "LDAP test username required"
|
||||
msgstr "LDAP测试用户名必填"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:82
|
||||
msgid "Company wide"
|
||||
msgstr "全公司"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:9
|
||||
msgid "login successful"
|
||||
msgstr "登录成功"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:10
|
||||
msgid "Failed to connect to LDAP service"
|
||||
msgstr "连接LDAP服务失败"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:11
|
||||
msgid "Password verification failed"
|
||||
msgstr "密码验证失败"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:12
|
||||
msgid "Application Token verification failed"
|
||||
msgstr "应用 Token验证失败"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:14
|
||||
msgid ""
|
||||
"You are not the application administrator or the session has expired (try"
|
||||
" logging out and logging in again)"
|
||||
msgstr "您不是应用管理员 或者 session失效(尝试一下退出重新登录)"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:17
|
||||
msgid "Resource type {} does not exist!"
|
||||
msgstr "资源类型 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:18
|
||||
msgid "Resource type {} already exists!"
|
||||
msgstr "资源类型 {} 已经存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:20
|
||||
msgid "Because there are resources under this type, they cannot be deleted!"
|
||||
msgstr "因为该类型下有资源的存在, 不能删除!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:22
|
||||
msgid "User {} does not exist!"
|
||||
msgstr "用户 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:23
|
||||
msgid "User {} already exists!"
|
||||
msgstr "用户 {} 已经存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:24
|
||||
msgid "Role {} does not exist!"
|
||||
msgstr "角色 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:25
|
||||
msgid "Role {} already exists!"
|
||||
msgstr "角色 {} 已经存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:26
|
||||
msgid "Global role {} does not exist!"
|
||||
msgstr "全局角色 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:27
|
||||
msgid "Global role {} already exists!"
|
||||
msgstr "全局角色 {} 已经存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:29
|
||||
msgid "You do not have {} permission on resource: {}"
|
||||
msgstr "您没有资源: {} 的 {} 权限"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:30
|
||||
msgid "Requires administrator permissions"
|
||||
msgstr "需要管理员权限"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:31
|
||||
msgid "Requires role: {}"
|
||||
msgstr "需要角色: {}"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:33
|
||||
msgid "To delete a user role, please operate on the User Management page!"
|
||||
msgstr "删除用户角色, 请在 用户管理 页面操作!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:35
|
||||
msgid "Application {} already exists"
|
||||
msgstr "应用 {} 已经存在"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:36
|
||||
msgid "Application {} does not exist!"
|
||||
msgstr "应用 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:37
|
||||
msgid "The Secret is invalid"
|
||||
msgstr "应用的Secret无效"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:39
|
||||
msgid "Resource {} does not exist!"
|
||||
msgstr "资源 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:40
|
||||
msgid "Resource {} already exists!"
|
||||
msgstr "资源 {} 已经存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:42
|
||||
msgid "Resource group {} does not exist!"
|
||||
msgstr "资源组 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:43
|
||||
msgid "Resource group {} already exists!"
|
||||
msgstr "资源组 {} 已经存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:45
|
||||
msgid "Inheritance detected infinite loop"
|
||||
msgstr "继承检测到了死循环"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:46
|
||||
msgid "Role relationship {} does not exist!"
|
||||
msgstr "角色关系 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:48
|
||||
msgid "Trigger {} does not exist!"
|
||||
msgstr "触发器 {} 不存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:49
|
||||
msgid "Trigger {} already exists!"
|
||||
msgstr "触发器 {} 已经存在!"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:50
|
||||
msgid "Trigger {} has been disabled!"
|
||||
msgstr "Trigger {} has been disabled!"
|
||||
|
||||
#~ msgid "Not a valid date value."
|
||||
#~ msgstr ""
|
||||
|
@@ -24,6 +24,7 @@ class AuditLogView(APIView):
|
||||
'role': AuditCRUD.search_role,
|
||||
'trigger': AuditCRUD.search_trigger,
|
||||
'resource': AuditCRUD.search_resource,
|
||||
'login': AuditCRUD.search_login,
|
||||
}
|
||||
if name not in func_map:
|
||||
abort(400, f'wrong {name}, please use {func_map.keys()}')
|
||||
|
@@ -8,11 +8,15 @@ from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import login_user, logout_user
|
||||
from flask_login import login_user
|
||||
from flask_login import logout_user
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import AuthenticateType
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
from api.lib.perm.acl.cache import User
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -34,8 +38,10 @@ class LoginView(APIView):
|
||||
username = request.values.get("username") or request.values.get("email")
|
||||
password = request.values.get("password")
|
||||
_role = None
|
||||
if current_app.config.get('AUTH_WITH_LDAP'):
|
||||
user, authenticated = User.query.authenticate_with_ldap(username, password)
|
||||
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
||||
if config.get('enabled') or config.get('enable'):
|
||||
from api.lib.perm.authentication.ldap import authenticate_with_ldap
|
||||
user, authenticated = authenticate_with_ldap(username, password)
|
||||
else:
|
||||
user, authenticated = User.query.authenticate(username, password)
|
||||
if not user:
|
||||
@@ -176,4 +182,7 @@ class LogoutView(APIView):
|
||||
@auth_abandoned
|
||||
def post(self):
|
||||
logout_user()
|
||||
|
||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
||||
|
||||
self.jsonify(code=200)
|
||||
|
@@ -35,6 +35,7 @@ class CIRelationSearchView(APIView):
|
||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||
|
||||
root_id = request.values.get('root_id')
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
||||
|
||||
query = request.values.get('q', "")
|
||||
@@ -42,9 +43,11 @@ class CIRelationSearchView(APIView):
|
||||
facet = handle_arg_list(request.values.get("facet", ""))
|
||||
sort = request.values.get("sort")
|
||||
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE')
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse)
|
||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
@@ -67,9 +70,11 @@ class CIRelationStatisticsView(APIView):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_ids, level)
|
||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
||||
try:
|
||||
result = s.statistics(type_ids)
|
||||
except SearchError as e:
|
||||
@@ -121,14 +126,18 @@ class CIRelationView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
|
||||
|
||||
def post(self, first_ci_id, second_ci_id):
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
manager = CIRelationManager()
|
||||
res = manager.add(first_ci_id, second_ci_id)
|
||||
res = manager.add(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(cr_id=res)
|
||||
|
||||
def delete(self, first_ci_id, second_ci_id):
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
manager = CIRelationManager()
|
||||
manager.delete_2(first_ci_id, second_ci_id)
|
||||
manager.delete_2(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(message="CIType Relation is deleted")
|
||||
|
||||
@@ -151,8 +160,9 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||
parents = list(map(int, request.values.get('parents', [])))
|
||||
children = list(map(int, request.values.get('children', [])))
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
CIRelationManager.batch_update(ci_ids, parents, children)
|
||||
CIRelationManager.batch_update(ci_ids, parents, children, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
@@ -166,7 +176,8 @@ class BatchCreateOrUpdateCIRelationView(APIView):
|
||||
def delete(self):
|
||||
ci_ids = list(map(int, request.values.get('ci_ids')))
|
||||
parents = list(map(int, request.values.get('parents', [])))
|
||||
ancestor_ids = request.values.get('ancestor_ids') or None
|
||||
|
||||
CIRelationManager.batch_delete(ci_ids, parents)
|
||||
CIRelationManager.batch_delete(ci_ids, parents, ancestor_ids=ancestor_ids)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
@@ -166,7 +166,8 @@ class CITypeAttributeView(APIView):
|
||||
t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, ErrFormat.ci_type_not_found)
|
||||
type_id = t.id
|
||||
unique_id = t.unique_id
|
||||
unique = AttributeCache.get(unique_id).name
|
||||
unique = AttributeCache.get(unique_id)
|
||||
unique = unique and unique.name
|
||||
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||
@@ -318,12 +319,14 @@ class CITypeAttributeGroupView(APIView):
|
||||
|
||||
|
||||
class CITypeTemplateView(APIView):
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export")
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def get(self): # export
|
||||
return self.jsonify(
|
||||
dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
def get(self, type_id=None): # export
|
||||
if type_id is not None:
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
|
||||
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def post(self): # import
|
||||
@@ -457,13 +460,21 @@ class CITypeGrantView(APIView):
|
||||
_type = CITypeCache.get(type_id)
|
||||
type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and \
|
||||
not is_app_admin('cmdb'):
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||
|
||||
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms)
|
||||
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||
|
||||
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
if request.values.get('ci_filter') or request.values.get('attr_filter'):
|
||||
CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||
else:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
current_app.logger.info((rid, app_id))
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
current_app.logger.info('done')
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
@@ -481,21 +492,27 @@ class CITypeRevokeView(APIView):
|
||||
_type = CITypeCache.get(type_id)
|
||||
type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and \
|
||||
not is_app_admin('cmdb'):
|
||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||
|
||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms)
|
||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||
|
||||
if PermEnum.READ in perms:
|
||||
CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||
app_id = AppCache.get('cmdb').id
|
||||
resource = None
|
||||
if PermEnum.READ in perms or not perms:
|
||||
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||
|
||||
app_id = AppCache.get('cmdb').id
|
||||
users = RoleRelationCRUD.get_users_by_rid(rid, app_id)
|
||||
for i in (users or []):
|
||||
if i.get('role', {}).get('id') and not RoleCRUD.has_permission(
|
||||
i.get('role').get('id'), type_name, ResourceTypeEnum.CI_TYPE, app_id, PermEnum.READ):
|
||||
PreferenceManager.delete_by_type_id(type_id, i.get('uid'))
|
||||
if not resource:
|
||||
from api.tasks.acl import role_rebuild
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
|
||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||
|
||||
users = RoleRelationCRUD.get_users_by_rid(rid, app_id)
|
||||
for i in (users or []):
|
||||
if i.get('role', {}).get('id') and not RoleCRUD.has_permission(
|
||||
i.get('role').get('id'), type_name, ResourceTypeEnum.CI_TYPE, app_id, PermEnum.READ):
|
||||
PreferenceManager.delete_by_type_id(type_id, i.get('uid'))
|
||||
|
||||
return self.jsonify(type_id=type_id, rid=rid)
|
||||
|
||||
|
@@ -9,6 +9,7 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -109,3 +110,10 @@ class CITypeRelationRevokeView(APIView):
|
||||
acl.revoke_resource_from_role_by_rid(resource_name, rid, ResourceTypeEnum.CI_TYPE_RELATION, perms)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class CITypeRelationCanEditView(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:parent_id>/<int:child_id>/can_edit"
|
||||
|
||||
def get(self, parent_id, child_id):
|
||||
return self.jsonify(result=PreferenceManager.can_edit_relation(parent_id, child_id))
|
||||
|
88
cmdb-api/api/views/common_setting/auth_config.py
Normal file
88
cmdb-api/api/views/common_setting/auth_config.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from flask import abort, request
|
||||
|
||||
from api.lib.common_setting.common_data import AuthenticateDataCRUD
|
||||
from api.lib.common_setting.const import TestType
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
prefix = '/auth_config'
|
||||
|
||||
|
||||
class AuthConfigView(APIView):
|
||||
url_prefix = (f'{prefix}/<string:auth_type>',)
|
||||
|
||||
@role_required("acl_admin")
|
||||
def get(self, auth_type):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
|
||||
if auth_type in cli.common_type_list:
|
||||
data = cli.get_record(True)
|
||||
else:
|
||||
data = cli.get_record_with_decrypt()
|
||||
return self.jsonify(data)
|
||||
|
||||
@role_required("acl_admin")
|
||||
def post(self, auth_type):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
|
||||
params = request.json
|
||||
data = params.get('data', {})
|
||||
if auth_type in cli.common_type_list:
|
||||
data['encrypt'] = False
|
||||
cli.create(data)
|
||||
|
||||
return self.jsonify(params)
|
||||
|
||||
|
||||
class AuthConfigViewWithId(APIView):
|
||||
url_prefix = (f'{prefix}/<string:auth_type>/<int:_id>',)
|
||||
|
||||
@role_required("acl_admin")
|
||||
def put(self, auth_type, _id):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
|
||||
params = request.json
|
||||
data = params.get('data', {})
|
||||
if auth_type in cli.common_type_list:
|
||||
data['encrypt'] = False
|
||||
|
||||
res = cli.update(_id, data)
|
||||
|
||||
return self.jsonify(res.to_dict())
|
||||
|
||||
@role_required("acl_admin")
|
||||
def delete(self, auth_type, _id):
|
||||
cli = AuthenticateDataCRUD(auth_type)
|
||||
|
||||
if auth_type not in cli.get_support_type_list():
|
||||
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
|
||||
cli.delete(_id)
|
||||
return self.jsonify({})
|
||||
|
||||
|
||||
class AuthEnableListView(APIView):
|
||||
url_prefix = (f'{prefix}/enable_list',)
|
||||
|
||||
method_decorators = []
|
||||
|
||||
def get(self):
|
||||
return self.jsonify(AuthenticateDataCRUD.get_enable_list())
|
||||
|
||||
|
||||
class AuthConfigTestView(APIView):
|
||||
url_prefix = (f'{prefix}/<string:auth_type>/test',)
|
||||
|
||||
def post(self, auth_type):
|
||||
test_type = request.values.get('test_type', TestType.Connect)
|
||||
params = request.json
|
||||
return self.jsonify(AuthenticateDataCRUD(auth_type).test(test_type, params.get('data')))
|
@@ -24,12 +24,12 @@ class DataView(APIView):
|
||||
class DataViewWithId(APIView):
|
||||
url_prefix = (f'{prefix}/<string:data_type>/<int:_id>',)
|
||||
|
||||
def put(self, _id):
|
||||
def put(self, data_type, _id):
|
||||
params = request.json
|
||||
res = CommonDataCRUD.update_data(_id, **params)
|
||||
|
||||
return self.jsonify(res.to_dict())
|
||||
|
||||
def delete(self, _id):
|
||||
def delete(self, data_type, _id):
|
||||
CommonDataCRUD.delete(_id)
|
||||
return self.jsonify({})
|
||||
|
@@ -62,7 +62,7 @@ class DepartmentView(APIView):
|
||||
class DepartmentIDView(APIView):
|
||||
url_prefix = (f'{prefix}/<int:_id>',)
|
||||
|
||||
def get(self, _id):
|
||||
def put(self, _id):
|
||||
form = DepartmentForm(MultiDict(request.json))
|
||||
if not form.validate():
|
||||
abort(400, ','.join(['{}: {}'.format(filed, ','.join(msg))
|
||||
|
@@ -3,9 +3,10 @@ import os
|
||||
|
||||
from flask import request, abort, current_app, send_from_directory
|
||||
from werkzeug.utils import secure_filename
|
||||
import lz4.frame
|
||||
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name
|
||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
||||
from api.resource import APIView
|
||||
|
||||
prefix = '/file'
|
||||
@@ -28,7 +29,8 @@ class GetFileView(APIView):
|
||||
url_prefix = (f'{prefix}/<string:_filename>',)
|
||||
|
||||
def get(self, _filename):
|
||||
return send_from_directory(current_app.config['UPLOAD_DIRECTORY_FULL'], _filename, as_attachment=True)
|
||||
file_stream = CommonFileCRUD.get_file(_filename)
|
||||
return self.send_file(file_stream, as_attachment=True, download_name=_filename)
|
||||
|
||||
|
||||
class PostFileView(APIView):
|
||||
@@ -44,6 +46,8 @@ class PostFileView(APIView):
|
||||
if not file:
|
||||
abort(400, ErrFormat.file_is_required)
|
||||
extension = file.mimetype.split('/')[-1]
|
||||
if '+' in extension:
|
||||
extension = file.filename.split('.')[-1]
|
||||
if file.filename == '':
|
||||
filename = f'.{extension}'
|
||||
else:
|
||||
@@ -53,11 +57,20 @@ class PostFileView(APIView):
|
||||
filename = file.filename
|
||||
|
||||
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)):
|
||||
filename = generate_new_file_name(filename)
|
||||
filename = secure_filename(filename)
|
||||
file.save(os.path.join(
|
||||
current_app.config['UPLOAD_DIRECTORY_FULL'], filename))
|
||||
new_filename = generate_new_file_name(filename)
|
||||
new_filename = secure_filename(new_filename)
|
||||
file_content = file.read()
|
||||
compressed_data = lz4.frame.compress(file_content)
|
||||
try:
|
||||
CommonFileCRUD.add_file(
|
||||
origin_name=filename,
|
||||
file_name=new_filename,
|
||||
binary=compressed_data,
|
||||
)
|
||||
|
||||
return self.jsonify(file_name=filename)
|
||||
return self.jsonify(file_name=new_filename)
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
abort(400, ErrFormat.upload_failed.format(e))
|
||||
|
||||
abort(400, 'Extension not allow')
|
||||
abort(400, ErrFormat.file_type_not_allowed.format(filename))
|
||||
|
1
cmdb-api/babel.cfg
Normal file
1
cmdb-api/babel.cfg
Normal file
@@ -0,0 +1 @@
|
||||
[python: api/**.py]
|
@@ -10,6 +10,7 @@ environs==4.2.0
|
||||
flasgger==0.9.5
|
||||
Flask==2.3.2
|
||||
Flask-Bcrypt==1.0.1
|
||||
flask-babel==4.0.0
|
||||
Flask-Caching==2.0.2
|
||||
Flask-Cors==4.0.0
|
||||
Flask-Login>=0.6.2
|
||||
@@ -34,7 +35,7 @@ cryptography>=41.0.2
|
||||
PyJWT==2.4.0
|
||||
PyMySQL==1.1.0
|
||||
ldap3==2.9.1
|
||||
PyYAML==6.0
|
||||
PyYAML==6.0.1
|
||||
redis==4.6.0
|
||||
requests==2.31.0
|
||||
requests_oauthlib==1.3.1
|
||||
@@ -48,6 +49,6 @@ treelib==1.6.1
|
||||
Werkzeug>=2.3.6
|
||||
WTForms==3.0.0
|
||||
shamir~=17.12.0
|
||||
hvac~=2.0.0
|
||||
pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
@@ -11,10 +11,10 @@ from environs import Env
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
ENV = env.str("FLASK_ENV", default="production")
|
||||
DEBUG = ENV == "development"
|
||||
SECRET_KEY = env.str("SECRET_KEY")
|
||||
BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
|
||||
ENV = env.str('FLASK_ENV', default='production')
|
||||
DEBUG = ENV == 'development'
|
||||
SECRET_KEY = env.str('SECRET_KEY')
|
||||
BCRYPT_LOG_ROUNDS = env.int('BCRYPT_LOG_ROUNDS', default=13)
|
||||
DEBUG_TB_ENABLED = DEBUG
|
||||
DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
|
||||
@@ -23,7 +23,7 @@ ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
||||
# # database
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
SQLALCHEMY_BINDS = {
|
||||
"user": 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
}
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
@@ -32,11 +32,11 @@ SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
}
|
||||
|
||||
# # cache
|
||||
CACHE_TYPE = "redis"
|
||||
CACHE_REDIS_HOST = "127.0.0.1"
|
||||
CACHE_TYPE = 'redis'
|
||||
CACHE_REDIS_HOST = '127.0.0.1'
|
||||
CACHE_REDIS_PORT = 6379
|
||||
CACHE_REDIS_PASSWORD = ""
|
||||
CACHE_KEY_PREFIX = "CMDB::"
|
||||
CACHE_REDIS_PASSWORD = ''
|
||||
CACHE_KEY_PREFIX = 'CMDB::'
|
||||
CACHE_DEFAULT_TIMEOUT = 3000
|
||||
|
||||
# # log
|
||||
@@ -55,10 +55,10 @@ DEFAULT_MAIL_SENDER = ''
|
||||
|
||||
# # queue
|
||||
CELERY = {
|
||||
"broker_url": 'redis://127.0.0.1:6379/2',
|
||||
"result_backend": "redis://127.0.0.1:6379/2",
|
||||
"broker_vhost": "/",
|
||||
"broker_connection_retry_on_startup": True
|
||||
'broker_url': 'redis://127.0.0.1:6379/2',
|
||||
'result_backend': 'redis://127.0.0.1:6379/2',
|
||||
'broker_vhost': '/',
|
||||
'broker_connection_retry_on_startup': True
|
||||
}
|
||||
ONCE = {
|
||||
'backend': 'celery_once.backends.Redis',
|
||||
@@ -67,33 +67,81 @@ ONCE = {
|
||||
}
|
||||
}
|
||||
|
||||
# # SSO
|
||||
CAS_SERVER = "http://sso.xxx.com"
|
||||
CAS_VALIDATE_SERVER = "http://sso.xxx.com"
|
||||
CAS_LOGIN_ROUTE = "/cas/login"
|
||||
CAS_LOGOUT_ROUTE = "/cas/logout"
|
||||
CAS_VALIDATE_ROUTE = "/cas/serviceValidate"
|
||||
CAS_AFTER_LOGIN = "/"
|
||||
DEFAULT_SERVICE = "http://127.0.0.1:8000"
|
||||
# =============================== Authentication ===========================================================
|
||||
|
||||
# # ldap
|
||||
AUTH_WITH_LDAP = False
|
||||
LDAP_SERVER = ''
|
||||
LDAP_DOMAIN = ''
|
||||
LDAP_USER_DN = 'cn={},ou=users,dc=xxx,dc=com'
|
||||
# # CAS
|
||||
CAS = dict(
|
||||
enabled=False,
|
||||
cas_server='https://{your-CASServer-hostname}',
|
||||
cas_validate_server='https://{your-CASServer-hostname}',
|
||||
cas_login_route='/cas/built-in/cas/login',
|
||||
cas_logout_route='/cas/built-in/cas/logout',
|
||||
cas_validate_route='/cas/built-in/cas/serviceValidate',
|
||||
cas_after_login='/',
|
||||
cas_user_map={
|
||||
'username': {'tag': 'cas:user'},
|
||||
'nickname': {'tag': 'cas:attribute', 'attrs': {'name': 'displayName'}},
|
||||
'email': {'tag': 'cas:attribute', 'attrs': {'name': 'email'}},
|
||||
'mobile': {'tag': 'cas:attribute', 'attrs': {'name': 'phone'}},
|
||||
'avatar': {'tag': 'cas:attribute', 'attrs': {'name': 'avatar'}},
|
||||
}
|
||||
)
|
||||
|
||||
# # OAuth2.0
|
||||
OAUTH2 = dict(
|
||||
enabled=False,
|
||||
client_id='',
|
||||
client_secret='',
|
||||
authorize_url='https://{your-OAuth2Server-hostname}/login/oauth/authorize',
|
||||
token_url='https://{your-OAuth2Server-hostname}/api/login/oauth/access_token',
|
||||
scopes=['profile', 'email'],
|
||||
user_info={
|
||||
'url': 'https://{your-OAuth2Server-hostname}/api/userinfo',
|
||||
'email': 'email',
|
||||
'username': 'name',
|
||||
'avatar': 'picture'
|
||||
},
|
||||
after_login='/'
|
||||
)
|
||||
|
||||
# # OIDC
|
||||
OIDC = dict(
|
||||
enabled=False,
|
||||
client_id='',
|
||||
client_secret='',
|
||||
authorize_url='https://{your-OIDCServer-hostname}/login/oauth/authorize',
|
||||
token_url='https://{your-OIDCServer-hostname}/api/login/oauth/access_token',
|
||||
scopes=['openid', 'profile', 'email'],
|
||||
user_info={
|
||||
'url': 'https://{your-OIDCServer-hostname}/api/userinfo',
|
||||
'email': 'email',
|
||||
'username': 'name',
|
||||
'avatar': 'picture'
|
||||
},
|
||||
after_login='/'
|
||||
)
|
||||
|
||||
# # LDAP
|
||||
LDAP = dict(
|
||||
enabled=False,
|
||||
ldap_server='',
|
||||
ldap_domain='',
|
||||
ldap_user_dn='cn={},ou=users,dc=xxx,dc=com'
|
||||
)
|
||||
# ==========================================================================================================
|
||||
|
||||
# # pagination
|
||||
DEFAULT_PAGE_COUNT = 50
|
||||
|
||||
# # permission
|
||||
WHITE_LIST = ["127.0.0.1"]
|
||||
WHITE_LIST = ['127.0.0.1']
|
||||
USE_ACL = True
|
||||
|
||||
# # elastic search
|
||||
ES_HOST = '127.0.0.1'
|
||||
USE_ES = False
|
||||
|
||||
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
|
||||
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, 'Yes', 'YES', 'yes', 'Y', 'y']
|
||||
|
||||
# # messenger
|
||||
USE_MESSENGER = True
|
||||
|
@@ -27,7 +27,8 @@
|
||||
"core-js": "^3.31.0",
|
||||
"echarts": "^5.3.2",
|
||||
"element-ui": "^2.15.10",
|
||||
"exceljs": "^4.3.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"html2canvas": "^1.0.0-rc.5",
|
||||
"is-buffer": "^2.0.5",
|
||||
"jquery": "^3.6.0",
|
||||
@@ -47,6 +48,7 @@
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-cropper": "^0.6.2",
|
||||
"vue-grid-layout": "2.3.12",
|
||||
"vue-i18n": "8.28.2",
|
||||
"vue-infinite-scroll": "^2.0.2",
|
||||
"vue-json-editor": "^1.4.3",
|
||||
"vue-ls": "^3.2.1",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1698273699449') format('woff2'),
|
||||
url('iconfont.woff?t=1698273699449') format('woff'),
|
||||
url('iconfont.ttf?t=1698273699449') format('truetype');
|
||||
src: url('iconfont.woff2?t=1702544951995') format('woff2'),
|
||||
url('iconfont.woff?t=1702544951995') format('woff'),
|
||||
url('iconfont.ttf?t=1702544951995') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,274 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.OAUTH2:before {
|
||||
content: "\e8d8";
|
||||
}
|
||||
|
||||
.OIDC:before {
|
||||
content: "\e8d6";
|
||||
}
|
||||
|
||||
.CAS:before {
|
||||
content: "\e8d7";
|
||||
}
|
||||
|
||||
.ops-setting-auth:before {
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.ops-setting-auth-selected:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.a-itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
|
||||
.itsm-qrdownload:before {
|
||||
content: "\e8d3";
|
||||
}
|
||||
|
||||
.oneterm-playback:before {
|
||||
content: "\e8d1";
|
||||
}
|
||||
|
||||
.oneterm-disconnect:before {
|
||||
content: "\e8d0";
|
||||
}
|
||||
|
||||
.ops-oneterm-publickey-selected:before {
|
||||
content: "\e8cf";
|
||||
}
|
||||
|
||||
.ops-oneterm-publickey:before {
|
||||
content: "\e8ce";
|
||||
}
|
||||
|
||||
.ops-oneterm-gateway:before {
|
||||
content: "\e8b9";
|
||||
}
|
||||
|
||||
.ops-oneterm-gateway-selected:before {
|
||||
content: "\e8bf";
|
||||
}
|
||||
|
||||
.ops-oneterm-account:before {
|
||||
content: "\e8c0";
|
||||
}
|
||||
|
||||
.ops-oneterm-account-selected:before {
|
||||
content: "\e8c1";
|
||||
}
|
||||
|
||||
.ops-oneterm-command:before {
|
||||
content: "\e8c2";
|
||||
}
|
||||
|
||||
.ops-oneterm-command-selected:before {
|
||||
content: "\e8c3";
|
||||
}
|
||||
|
||||
.ops-oneterm-assetlist:before {
|
||||
content: "\e8c4";
|
||||
}
|
||||
|
||||
.ops-oneterm-assetlist-selected:before {
|
||||
content: "\e8c5";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessiononline:before {
|
||||
content: "\e8c6";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessiononline-selected:before {
|
||||
content: "\e8c7";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessionhistory-selected:before {
|
||||
content: "\e8c8";
|
||||
}
|
||||
|
||||
.ops-oneterm-sessionhistory:before {
|
||||
content: "\e8c9";
|
||||
}
|
||||
|
||||
.ops-oneterm-login:before {
|
||||
content: "\e8ca";
|
||||
}
|
||||
|
||||
.ops-oneterm-login-selected:before {
|
||||
content: "\e8cb";
|
||||
}
|
||||
|
||||
.ops-oneterm-operation:before {
|
||||
content: "\e8cc";
|
||||
}
|
||||
|
||||
.ops-oneterm-operation-selected:before {
|
||||
content: "\e8cd";
|
||||
}
|
||||
|
||||
.ops-oneterm-workstation-selected:before {
|
||||
content: "\e8b7";
|
||||
}
|
||||
|
||||
.ops-oneterm-workstation:before {
|
||||
content: "\e8b8";
|
||||
}
|
||||
|
||||
.oneterm-file-selected:before {
|
||||
content: "\e8be";
|
||||
}
|
||||
|
||||
.oneterm-file:before {
|
||||
content: "\e8bc";
|
||||
}
|
||||
|
||||
.oneterm-time:before {
|
||||
content: "\e8bd";
|
||||
}
|
||||
|
||||
.oneterm-download:before {
|
||||
content: "\e8bb";
|
||||
}
|
||||
|
||||
.oneterm-commandrecord:before {
|
||||
content: "\e8ba";
|
||||
}
|
||||
|
||||
.oneterm-asset:before {
|
||||
content: "\e8b6";
|
||||
}
|
||||
|
||||
.oneterm-total_asset:before {
|
||||
content: "\e8b5";
|
||||
}
|
||||
|
||||
.oneterm-switch:before {
|
||||
content: "\e8b4";
|
||||
}
|
||||
|
||||
.oneterm-session:before {
|
||||
content: "\e8b3";
|
||||
}
|
||||
|
||||
.oneterm-connect:before {
|
||||
content: "\e8b2";
|
||||
}
|
||||
|
||||
.oneterm-login:before {
|
||||
content: "\e8b1";
|
||||
}
|
||||
|
||||
.ops-oneterm-dashboard:before {
|
||||
content: "\e8af";
|
||||
}
|
||||
|
||||
.ops-oneterm-dashboard-selected:before {
|
||||
content: "\e8b0";
|
||||
}
|
||||
|
||||
.oneterm-recentsession:before {
|
||||
content: "\e8ae";
|
||||
}
|
||||
|
||||
.oneterm-myassets:before {
|
||||
content: "\e8ad";
|
||||
}
|
||||
|
||||
.ops-oneterm-log:before {
|
||||
content: "\e8aa";
|
||||
}
|
||||
|
||||
.ops-oneterm-session-selected:before {
|
||||
content: "\e8ab";
|
||||
}
|
||||
|
||||
.ops-oneterm-session:before {
|
||||
content: "\e8ac";
|
||||
}
|
||||
|
||||
.ops-oneterm-log-selected:before {
|
||||
content: "\e8a9";
|
||||
}
|
||||
|
||||
.ops-oneterm-assets:before {
|
||||
content: "\e8a7";
|
||||
}
|
||||
|
||||
.ops-oneterm-assets-selected:before {
|
||||
content: "\e8a8";
|
||||
}
|
||||
|
||||
.itsm-down:before {
|
||||
content: "\e8a5";
|
||||
}
|
||||
|
||||
.itsm-up:before {
|
||||
content: "\e8a6";
|
||||
}
|
||||
|
||||
.itsm-download:before {
|
||||
content: "\e8a4";
|
||||
}
|
||||
|
||||
.itsm-print:before {
|
||||
content: "\e8a3";
|
||||
}
|
||||
|
||||
.itsm-view:before {
|
||||
content: "\e8a2";
|
||||
}
|
||||
|
||||
.itsm-word:before {
|
||||
content: "\e8a1";
|
||||
}
|
||||
|
||||
.datainsight-custom:before {
|
||||
content: "\e89e";
|
||||
}
|
||||
|
||||
.datainsight-prometheus:before {
|
||||
content: "\e89f";
|
||||
}
|
||||
|
||||
.datainsight-zabbix:before {
|
||||
content: "\e8a0";
|
||||
}
|
||||
|
||||
.setting-mainpeople:before {
|
||||
content: "\e89a";
|
||||
}
|
||||
|
||||
.setting-deputypeople:before {
|
||||
content: "\e89d";
|
||||
}
|
||||
|
||||
.ops-setting-duty:before {
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.ops-setting-duty-selected:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.datainsight-sequential:before {
|
||||
content: "\e899";
|
||||
}
|
||||
|
||||
.datainsight-close:before {
|
||||
content: "\e898";
|
||||
}
|
||||
|
||||
.datainsight-handle:before {
|
||||
content: "\e897";
|
||||
}
|
||||
|
||||
.datainsight-table:before {
|
||||
content: "\e896";
|
||||
}
|
||||
|
||||
.icon-xianxing-password:before {
|
||||
content: "\e894";
|
||||
}
|
||||
@@ -21,11 +289,11 @@
|
||||
content: "\e895";
|
||||
}
|
||||
|
||||
.a-itsm-oneclickdownload:before {
|
||||
.itsm-download-all:before {
|
||||
content: "\e892";
|
||||
}
|
||||
|
||||
.a-itsm-packagedownload:before {
|
||||
.itsm-download-package:before {
|
||||
content: "\e893";
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,475 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "38566548",
|
||||
"name": "OAuth2.0",
|
||||
"font_class": "OAUTH2",
|
||||
"unicode": "e8d8",
|
||||
"unicode_decimal": 59608
|
||||
},
|
||||
{
|
||||
"icon_id": "38566584",
|
||||
"name": "OIDC",
|
||||
"font_class": "OIDC",
|
||||
"unicode": "e8d6",
|
||||
"unicode_decimal": 59606
|
||||
},
|
||||
{
|
||||
"icon_id": "38566578",
|
||||
"name": "cas",
|
||||
"font_class": "CAS",
|
||||
"unicode": "e8d7",
|
||||
"unicode_decimal": 59607
|
||||
},
|
||||
{
|
||||
"icon_id": "38547395",
|
||||
"name": "setting-authentication",
|
||||
"font_class": "ops-setting-auth",
|
||||
"unicode": "e8d5",
|
||||
"unicode_decimal": 59605
|
||||
},
|
||||
{
|
||||
"icon_id": "38547389",
|
||||
"name": "setting-authentication-selected",
|
||||
"font_class": "ops-setting-auth-selected",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "38533133",
|
||||
"name": "itsm-knowledge (2)",
|
||||
"font_class": "a-itsm-knowledge2",
|
||||
"unicode": "e8d2",
|
||||
"unicode_decimal": 59602
|
||||
},
|
||||
{
|
||||
"icon_id": "38531868",
|
||||
"name": "itsm-QRcode",
|
||||
"font_class": "itsm-qrdownload",
|
||||
"unicode": "e8d3",
|
||||
"unicode_decimal": 59603
|
||||
},
|
||||
{
|
||||
"icon_id": "38413515",
|
||||
"name": "oneterm-playback",
|
||||
"font_class": "oneterm-playback",
|
||||
"unicode": "e8d1",
|
||||
"unicode_decimal": 59601
|
||||
},
|
||||
{
|
||||
"icon_id": "38413481",
|
||||
"name": "oneterm-disconnect",
|
||||
"font_class": "oneterm-disconnect",
|
||||
"unicode": "e8d0",
|
||||
"unicode_decimal": 59600
|
||||
},
|
||||
{
|
||||
"icon_id": "38407867",
|
||||
"name": "oneterm-key-selected",
|
||||
"font_class": "ops-oneterm-publickey-selected",
|
||||
"unicode": "e8cf",
|
||||
"unicode_decimal": 59599
|
||||
},
|
||||
{
|
||||
"icon_id": "38407915",
|
||||
"name": "oneterm-key",
|
||||
"font_class": "ops-oneterm-publickey",
|
||||
"unicode": "e8ce",
|
||||
"unicode_decimal": 59598
|
||||
},
|
||||
{
|
||||
"icon_id": "38311855",
|
||||
"name": "oneterm-gateway",
|
||||
"font_class": "ops-oneterm-gateway",
|
||||
"unicode": "e8b9",
|
||||
"unicode_decimal": 59577
|
||||
},
|
||||
{
|
||||
"icon_id": "38311938",
|
||||
"name": "oneterm-gateway-selected",
|
||||
"font_class": "ops-oneterm-gateway-selected",
|
||||
"unicode": "e8bf",
|
||||
"unicode_decimal": 59583
|
||||
},
|
||||
{
|
||||
"icon_id": "38311957",
|
||||
"name": "oneterm-account",
|
||||
"font_class": "ops-oneterm-account",
|
||||
"unicode": "e8c0",
|
||||
"unicode_decimal": 59584
|
||||
},
|
||||
{
|
||||
"icon_id": "38311961",
|
||||
"name": "oneterm-account-selected",
|
||||
"font_class": "ops-oneterm-account-selected",
|
||||
"unicode": "e8c1",
|
||||
"unicode_decimal": 59585
|
||||
},
|
||||
{
|
||||
"icon_id": "38311974",
|
||||
"name": "oneterm-command",
|
||||
"font_class": "ops-oneterm-command",
|
||||
"unicode": "e8c2",
|
||||
"unicode_decimal": 59586
|
||||
},
|
||||
{
|
||||
"icon_id": "38311976",
|
||||
"name": "oneterm-command-selected",
|
||||
"font_class": "ops-oneterm-command-selected",
|
||||
"unicode": "e8c3",
|
||||
"unicode_decimal": 59587
|
||||
},
|
||||
{
|
||||
"icon_id": "38311979",
|
||||
"name": "oneterm-asset_list",
|
||||
"font_class": "ops-oneterm-assetlist",
|
||||
"unicode": "e8c4",
|
||||
"unicode_decimal": 59588
|
||||
},
|
||||
{
|
||||
"icon_id": "38311985",
|
||||
"name": "oneterm-asset_list-selected",
|
||||
"font_class": "ops-oneterm-assetlist-selected",
|
||||
"unicode": "e8c5",
|
||||
"unicode_decimal": 59589
|
||||
},
|
||||
{
|
||||
"icon_id": "38312030",
|
||||
"name": "oneterm-online",
|
||||
"font_class": "ops-oneterm-sessiononline",
|
||||
"unicode": "e8c6",
|
||||
"unicode_decimal": 59590
|
||||
},
|
||||
{
|
||||
"icon_id": "38312152",
|
||||
"name": "oneterm-online-selected",
|
||||
"font_class": "ops-oneterm-sessiononline-selected",
|
||||
"unicode": "e8c7",
|
||||
"unicode_decimal": 59591
|
||||
},
|
||||
{
|
||||
"icon_id": "38312154",
|
||||
"name": "oneterm-history-selected",
|
||||
"font_class": "ops-oneterm-sessionhistory-selected",
|
||||
"unicode": "e8c8",
|
||||
"unicode_decimal": 59592
|
||||
},
|
||||
{
|
||||
"icon_id": "38312155",
|
||||
"name": "oneterm-history",
|
||||
"font_class": "ops-oneterm-sessionhistory",
|
||||
"unicode": "e8c9",
|
||||
"unicode_decimal": 59593
|
||||
},
|
||||
{
|
||||
"icon_id": "38312404",
|
||||
"name": "oneterm-entry_log",
|
||||
"font_class": "ops-oneterm-login",
|
||||
"unicode": "e8ca",
|
||||
"unicode_decimal": 59594
|
||||
},
|
||||
{
|
||||
"icon_id": "38312423",
|
||||
"name": "oneterm-entry_log-selected",
|
||||
"font_class": "ops-oneterm-login-selected",
|
||||
"unicode": "e8cb",
|
||||
"unicode_decimal": 59595
|
||||
},
|
||||
{
|
||||
"icon_id": "38312426",
|
||||
"name": "oneterm-operation_log",
|
||||
"font_class": "ops-oneterm-operation",
|
||||
"unicode": "e8cc",
|
||||
"unicode_decimal": 59596
|
||||
},
|
||||
{
|
||||
"icon_id": "38312445",
|
||||
"name": "oneterm-operation_log-selected",
|
||||
"font_class": "ops-oneterm-operation-selected",
|
||||
"unicode": "e8cd",
|
||||
"unicode_decimal": 59597
|
||||
},
|
||||
{
|
||||
"icon_id": "38307876",
|
||||
"name": "oneterm-workstation-selected",
|
||||
"font_class": "ops-oneterm-workstation-selected",
|
||||
"unicode": "e8b7",
|
||||
"unicode_decimal": 59575
|
||||
},
|
||||
{
|
||||
"icon_id": "38307871",
|
||||
"name": "oneterm-workstation",
|
||||
"font_class": "ops-oneterm-workstation",
|
||||
"unicode": "e8b8",
|
||||
"unicode_decimal": 59576
|
||||
},
|
||||
{
|
||||
"icon_id": "38302246",
|
||||
"name": "oneterm-file-selected",
|
||||
"font_class": "oneterm-file-selected",
|
||||
"unicode": "e8be",
|
||||
"unicode_decimal": 59582
|
||||
},
|
||||
{
|
||||
"icon_id": "38302255",
|
||||
"name": "oneterm-file",
|
||||
"font_class": "oneterm-file",
|
||||
"unicode": "e8bc",
|
||||
"unicode_decimal": 59580
|
||||
},
|
||||
{
|
||||
"icon_id": "38203528",
|
||||
"name": "oneterm-time",
|
||||
"font_class": "oneterm-time",
|
||||
"unicode": "e8bd",
|
||||
"unicode_decimal": 59581
|
||||
},
|
||||
{
|
||||
"icon_id": "38203331",
|
||||
"name": "oneterm-download",
|
||||
"font_class": "oneterm-download",
|
||||
"unicode": "e8bb",
|
||||
"unicode_decimal": 59579
|
||||
},
|
||||
{
|
||||
"icon_id": "38201351",
|
||||
"name": "oneterm-command record",
|
||||
"font_class": "oneterm-commandrecord",
|
||||
"unicode": "e8ba",
|
||||
"unicode_decimal": 59578
|
||||
},
|
||||
{
|
||||
"icon_id": "38199341",
|
||||
"name": "oneterm-connected assets",
|
||||
"font_class": "oneterm-asset",
|
||||
"unicode": "e8b6",
|
||||
"unicode_decimal": 59574
|
||||
},
|
||||
{
|
||||
"icon_id": "38199350",
|
||||
"name": "oneterm-total assets",
|
||||
"font_class": "oneterm-total_asset",
|
||||
"unicode": "e8b5",
|
||||
"unicode_decimal": 59573
|
||||
},
|
||||
{
|
||||
"icon_id": "38199303",
|
||||
"name": "oneterm-switch (3)",
|
||||
"font_class": "oneterm-switch",
|
||||
"unicode": "e8b4",
|
||||
"unicode_decimal": 59572
|
||||
},
|
||||
{
|
||||
"icon_id": "38199317",
|
||||
"name": "oneterm-session",
|
||||
"font_class": "oneterm-session",
|
||||
"unicode": "e8b3",
|
||||
"unicode_decimal": 59571
|
||||
},
|
||||
{
|
||||
"icon_id": "38199339",
|
||||
"name": "oneterm-connection",
|
||||
"font_class": "oneterm-connect",
|
||||
"unicode": "e8b2",
|
||||
"unicode_decimal": 59570
|
||||
},
|
||||
{
|
||||
"icon_id": "38198321",
|
||||
"name": "oneterm-log in",
|
||||
"font_class": "oneterm-login",
|
||||
"unicode": "e8b1",
|
||||
"unicode_decimal": 59569
|
||||
},
|
||||
{
|
||||
"icon_id": "38194554",
|
||||
"name": "oneterm-dashboard",
|
||||
"font_class": "ops-oneterm-dashboard",
|
||||
"unicode": "e8af",
|
||||
"unicode_decimal": 59567
|
||||
},
|
||||
{
|
||||
"icon_id": "38194525",
|
||||
"name": "oneterm-dashboard-selected",
|
||||
"font_class": "ops-oneterm-dashboard-selected",
|
||||
"unicode": "e8b0",
|
||||
"unicode_decimal": 59568
|
||||
},
|
||||
{
|
||||
"icon_id": "38194352",
|
||||
"name": "oneterm-recent session",
|
||||
"font_class": "oneterm-recentsession",
|
||||
"unicode": "e8ae",
|
||||
"unicode_decimal": 59566
|
||||
},
|
||||
{
|
||||
"icon_id": "38194383",
|
||||
"name": "oneterm-my assets",
|
||||
"font_class": "oneterm-myassets",
|
||||
"unicode": "e8ad",
|
||||
"unicode_decimal": 59565
|
||||
},
|
||||
{
|
||||
"icon_id": "38194089",
|
||||
"name": "oneterm-log",
|
||||
"font_class": "ops-oneterm-log",
|
||||
"unicode": "e8aa",
|
||||
"unicode_decimal": 59562
|
||||
},
|
||||
{
|
||||
"icon_id": "38194088",
|
||||
"name": "oneterm-conversation-selected",
|
||||
"font_class": "ops-oneterm-session-selected",
|
||||
"unicode": "e8ab",
|
||||
"unicode_decimal": 59563
|
||||
},
|
||||
{
|
||||
"icon_id": "38194065",
|
||||
"name": "oneterm-conversation",
|
||||
"font_class": "ops-oneterm-session",
|
||||
"unicode": "e8ac",
|
||||
"unicode_decimal": 59564
|
||||
},
|
||||
{
|
||||
"icon_id": "38194105",
|
||||
"name": "oneterm-log-selected",
|
||||
"font_class": "ops-oneterm-log-selected",
|
||||
"unicode": "e8a9",
|
||||
"unicode_decimal": 59561
|
||||
},
|
||||
{
|
||||
"icon_id": "38194054",
|
||||
"name": "oneterm-assets",
|
||||
"font_class": "ops-oneterm-assets",
|
||||
"unicode": "e8a7",
|
||||
"unicode_decimal": 59559
|
||||
},
|
||||
{
|
||||
"icon_id": "38194055",
|
||||
"name": "oneterm-assets-selected",
|
||||
"font_class": "ops-oneterm-assets-selected",
|
||||
"unicode": "e8a8",
|
||||
"unicode_decimal": 59560
|
||||
},
|
||||
{
|
||||
"icon_id": "38123087",
|
||||
"name": "itsm-down",
|
||||
"font_class": "itsm-down",
|
||||
"unicode": "e8a5",
|
||||
"unicode_decimal": 59557
|
||||
},
|
||||
{
|
||||
"icon_id": "38123084",
|
||||
"name": "itsm-up",
|
||||
"font_class": "itsm-up",
|
||||
"unicode": "e8a6",
|
||||
"unicode_decimal": 59558
|
||||
},
|
||||
{
|
||||
"icon_id": "38105374",
|
||||
"name": "itsm-download",
|
||||
"font_class": "itsm-download",
|
||||
"unicode": "e8a4",
|
||||
"unicode_decimal": 59556
|
||||
},
|
||||
{
|
||||
"icon_id": "38105235",
|
||||
"name": "itsm-print",
|
||||
"font_class": "itsm-print",
|
||||
"unicode": "e8a3",
|
||||
"unicode_decimal": 59555
|
||||
},
|
||||
{
|
||||
"icon_id": "38104997",
|
||||
"name": "itsm-view",
|
||||
"font_class": "itsm-view",
|
||||
"unicode": "e8a2",
|
||||
"unicode_decimal": 59554
|
||||
},
|
||||
{
|
||||
"icon_id": "38105129",
|
||||
"name": "itsm-word",
|
||||
"font_class": "itsm-word",
|
||||
"unicode": "e8a1",
|
||||
"unicode_decimal": 59553
|
||||
},
|
||||
{
|
||||
"icon_id": "38095730",
|
||||
"name": "datainsight-custom",
|
||||
"font_class": "datainsight-custom",
|
||||
"unicode": "e89e",
|
||||
"unicode_decimal": 59550
|
||||
},
|
||||
{
|
||||
"icon_id": "38095729",
|
||||
"name": "datainsight-prometheus",
|
||||
"font_class": "datainsight-prometheus",
|
||||
"unicode": "e89f",
|
||||
"unicode_decimal": 59551
|
||||
},
|
||||
{
|
||||
"icon_id": "38095728",
|
||||
"name": "datainsight-zabbix",
|
||||
"font_class": "datainsight-zabbix",
|
||||
"unicode": "e8a0",
|
||||
"unicode_decimal": 59552
|
||||
},
|
||||
{
|
||||
"icon_id": "37944507",
|
||||
"name": "setting-main people",
|
||||
"font_class": "setting-mainpeople",
|
||||
"unicode": "e89a",
|
||||
"unicode_decimal": 59546
|
||||
},
|
||||
{
|
||||
"icon_id": "37944503",
|
||||
"name": "setting-deputy people",
|
||||
"font_class": "setting-deputypeople",
|
||||
"unicode": "e89d",
|
||||
"unicode_decimal": 59549
|
||||
},
|
||||
{
|
||||
"icon_id": "37940080",
|
||||
"name": "ops-setting-duty",
|
||||
"font_class": "ops-setting-duty",
|
||||
"unicode": "e89c",
|
||||
"unicode_decimal": 59548
|
||||
},
|
||||
{
|
||||
"icon_id": "37940033",
|
||||
"name": "ops-setting-duty-selected",
|
||||
"font_class": "ops-setting-duty-selected",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "37841524",
|
||||
"name": "datainsight-sequential",
|
||||
"font_class": "datainsight-sequential",
|
||||
"unicode": "e899",
|
||||
"unicode_decimal": 59545
|
||||
},
|
||||
{
|
||||
"icon_id": "37841535",
|
||||
"name": "datainsight-close",
|
||||
"font_class": "datainsight-close",
|
||||
"unicode": "e898",
|
||||
"unicode_decimal": 59544
|
||||
},
|
||||
{
|
||||
"icon_id": "37841537",
|
||||
"name": "datainsight-handle",
|
||||
"font_class": "datainsight-handle",
|
||||
"unicode": "e897",
|
||||
"unicode_decimal": 59543
|
||||
},
|
||||
{
|
||||
"icon_id": "37841515",
|
||||
"name": "datainsight-table",
|
||||
"font_class": "datainsight-table",
|
||||
"unicode": "e896",
|
||||
"unicode_decimal": 59542
|
||||
},
|
||||
{
|
||||
"icon_id": "37830610",
|
||||
"name": "icon-xianxing-password",
|
||||
@@ -22,14 +491,14 @@
|
||||
{
|
||||
"icon_id": "37822199",
|
||||
"name": "itsm-oneclick download",
|
||||
"font_class": "a-itsm-oneclickdownload",
|
||||
"font_class": "itsm-download-all",
|
||||
"unicode": "e892",
|
||||
"unicode_decimal": 59538
|
||||
},
|
||||
{
|
||||
"icon_id": "37822198",
|
||||
"name": "itsm-package download",
|
||||
"font_class": "a-itsm-packagedownload",
|
||||
"font_class": "itsm-download-package",
|
||||
"unicode": "e893",
|
||||
"unicode_decimal": 59539
|
||||
},
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-config-provider :locale="locale">
|
||||
<a-config-provider :locale="antdLocale">
|
||||
<div id="app" :class="{ 'ops-fullscreen': isOpsFullScreen, 'ops-only-topmenu': isOpsOnlyTopMenu }">
|
||||
<router-view v-if="alive" />
|
||||
</div>
|
||||
@@ -7,8 +7,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
import { mapState, mapActions, mapMutations } from 'vuex'
|
||||
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'
|
||||
|
||||
@@ -24,20 +25,28 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
locale: zhCN,
|
||||
alive: true,
|
||||
timer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['locale']),
|
||||
antdLocale() {
|
||||
if (this.locale === 'zh') {
|
||||
return zhCN
|
||||
}
|
||||
return enUS
|
||||
},
|
||||
isOpsFullScreen() {
|
||||
return this.$route.name === 'cmdb_screen'
|
||||
return ['cmdb_screen'].includes(this.$route.name)
|
||||
},
|
||||
isOpsOnlyTopMenu() {
|
||||
return ['fullscreen_index', 'setting_person'].includes(this.$route.name)
|
||||
return ['fullscreen_index', 'setting_person', 'notice_center'].includes(this.$route.name)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
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)
|
||||
@@ -184,6 +193,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setTime']),
|
||||
...mapMutations(['SET_LOCALE']),
|
||||
reload() {
|
||||
this.alive = false
|
||||
this.$nextTick(() => {
|
||||
|
39
cmdb-ui/src/api/auth.js
Normal file
39
cmdb-ui/src/api/auth.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getAuthData(data_type) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/${data_type}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function postAuthData(data_type, data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/${data_type}`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function putAuthData(data_type, id, data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/${data_type}/${id}`,
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function getAuthDataEnable() {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/enable_list`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function testLDAP(test_type, data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
@@ -1,8 +1,6 @@
|
||||
import config from '@/config/setting'
|
||||
|
||||
const api = {
|
||||
Login: config.useSSO ? '/api/sso/login' : '/v1/acl/login',
|
||||
Logout: config.useSSO ? '/api/sso/logout' : '/v1/acl/logout',
|
||||
Login: '/v1/acl/login',
|
||||
Logout: '/v1/acl/logout',
|
||||
ForgePassword: '/auth/forge-password',
|
||||
Register: '/auth/register',
|
||||
twoStepCode: '/auth/2step-code',
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import api from './index'
|
||||
import { axios } from '@/utils/request'
|
||||
import config from '@/config/setting'
|
||||
/**
|
||||
* login func
|
||||
* parameter: {
|
||||
@@ -12,9 +11,10 @@ import config from '@/config/setting'
|
||||
* @param parameter
|
||||
* @returns {*}
|
||||
*/
|
||||
export function login(data) {
|
||||
if (config.useSSO) {
|
||||
window.location.href = config.ssoLoginUrl
|
||||
export function login(data, auth_type) {
|
||||
if (auth_type) {
|
||||
localStorage.setItem('ops_auth_type', auth_type)
|
||||
window.location.href = `/api/${auth_type.toLowerCase()}/login`
|
||||
} else {
|
||||
return axios({
|
||||
url: api.Login,
|
||||
@@ -43,17 +43,15 @@ export function getInfo() {
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
if (config.useSSO) {
|
||||
window.location.replace(api.Logout)
|
||||
} else {
|
||||
return axios({
|
||||
url: api.Logout,
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
})
|
||||
}
|
||||
const auth_type = localStorage.getItem('ops_auth_type')
|
||||
localStorage.clear()
|
||||
return axios({
|
||||
url: auth_type ? `/${auth_type.toLowerCase()}/logout` : api.Logout,
|
||||
method: auth_type ? 'get' : 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
BIN
cmdb-ui/src/assets/ops_logout.png
Normal file
BIN
cmdb-ui/src/assets/ops_logout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
@@ -1,29 +1,37 @@
|
||||
export const ruleTypeList = [
|
||||
{ value: 'and', label: '与' },
|
||||
{ value: 'or', label: '或' },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
import i18n from '@/lang'
|
||||
|
||||
export const expList = [
|
||||
{ value: 'is', label: '等于' },
|
||||
{ value: '~is', label: '不等于' },
|
||||
{ value: 'contain', label: '包含' },
|
||||
{ value: '~contain', label: '不包含' },
|
||||
{ value: 'start_with', label: '以...开始' },
|
||||
{ value: '~start_with', label: '不以...开始' },
|
||||
{ value: 'end_with', label: '以...结束' },
|
||||
{ value: '~end_with', label: '不以...结束' },
|
||||
{ value: '~value', label: '为空' }, // 为空的定义有点绕
|
||||
{ value: 'value', label: '不为空' },
|
||||
]
|
||||
export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = [
|
||||
{ value: 'in', label: 'in查询' },
|
||||
{ value: '~in', label: '非in查询' },
|
||||
{ value: 'range', label: '范围' },
|
||||
{ value: '~range', label: '范围外' },
|
||||
{ value: 'compare', label: '比较' },
|
||||
]
|
||||
export const expList = () => {
|
||||
return [
|
||||
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
|
||||
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
|
||||
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
|
||||
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
|
||||
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
|
||||
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
|
||||
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
|
||||
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = () => {
|
||||
return [
|
||||
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
|
||||
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
|
||||
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
|
||||
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
|
||||
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
|
||||
]
|
||||
}
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
|
@@ -1,293 +1,332 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '50px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '50px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '130px', '--custom-height': '24px' }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '24px' }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...getExpListByProperty(item.property), ...advancedExpList]"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
placeholder="请选择"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input-group
|
||||
size="small"
|
||||
compact
|
||||
v-else-if="item.exp === 'range' || item.exp === '~range'"
|
||||
:style="{ width: '175px' }"
|
||||
>
|
||||
<a-input class="ops-input" size="small" v-model="item.min" :style="{ width: '78px' }" placeholder="最小值" />
|
||||
~
|
||||
<a-input class="ops-input" size="small" v-model="item.max" :style="{ width: '78px' }" placeholder="最大值" />
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': '24px' }"
|
||||
v-model="item.compareType"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else-if="item.exp !== 'value' && item.exp !== '~value'"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? '以 ; 分隔' : ''"
|
||||
class="ops-input"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<a-tooltip title="复制">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="删除">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<div class="table-filter-add">
|
||||
<a @click="handleAddRule">+ 新增</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ruleTypeList,
|
||||
expList,
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: '等于' },
|
||||
{ value: '~is', label: '不等于' },
|
||||
{ value: '~value', label: '为空' }, // 为空的定义有点绕
|
||||
{ value: 'value', label: '不为空' },
|
||||
]
|
||||
}
|
||||
return this.expList
|
||||
}
|
||||
return this.expList
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<div>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '70px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '130px', '--custom-height': '24px' }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '24px' }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...getExpListByProperty(item.property), ...advancedExpList]"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input-group
|
||||
size="small"
|
||||
compact
|
||||
v-else-if="item.exp === 'range' || item.exp === '~range'"
|
||||
:style="{ width: '175px' }"
|
||||
>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.min"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('min')"
|
||||
/>
|
||||
~
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.max"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('max')"
|
||||
/>
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': '24px' }"
|
||||
v-model="item.compareType"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else-if="item.exp !== 'value' && item.exp !== '~value'"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:style="{ width: '175px' }"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<div class="table-filter-add">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
ruleTypeList() {
|
||||
return ruleTypeList()
|
||||
},
|
||||
expList() {
|
||||
return expList()
|
||||
},
|
||||
advancedExpList() {
|
||||
return advancedExpList()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
return this.expList
|
||||
}
|
||||
return this.expList
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleAddRuleAt(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 0, {
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
@@ -1,290 +1,296 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
v-if="isDropdown"
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
overlayClassName="table-filter"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<slot name="popover_item">
|
||||
<a-button type="primary" ghost>条件过滤<a-icon type="filter"/></a-button>
|
||||
</slot>
|
||||
<template slot="content">
|
||||
<Expression
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:534px">
|
||||
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
||||
<a-button type="primary" size="small" @click="handleSubmit">确定</a-button>
|
||||
<a-button size="small" @click="handleClear">清空</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<Expression
|
||||
v-else
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Expression from './expression.vue'
|
||||
import { advancedExpList, compareTypeList } from './constants'
|
||||
|
||||
export default {
|
||||
name: 'FilterComp',
|
||||
components: { Expression },
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottomRight',
|
||||
},
|
||||
isDropdown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
v-if="isDropdown"
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
overlayClassName="table-filter"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<slot name="popover_item">
|
||||
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
|
||||
</slot>
|
||||
<template slot="content">
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:554px">
|
||||
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
||||
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
||||
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-else
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Expression from './expression.vue'
|
||||
import { advancedExpList, compareTypeList } from './constants'
|
||||
|
||||
export default {
|
||||
name: 'FilterComp',
|
||||
components: { Expression },
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottomRight',
|
||||
},
|
||||
isDropdown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -89,20 +89,23 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const keyMapList = [
|
||||
{ value: 'default', label: '默认' },
|
||||
{ value: 'vim', label: 'vim' },
|
||||
{ value: 'emacs', label: 'emacs' },
|
||||
{ value: 'sublime', label: 'sublime' },
|
||||
]
|
||||
return {
|
||||
keyMapList,
|
||||
coder: null,
|
||||
fontSize: 14,
|
||||
keyMap: 'default',
|
||||
fullscreenExitVisible: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
keyMapList() {
|
||||
return [
|
||||
{ value: 'default', label: this.$t('default') },
|
||||
{ value: 'vim', label: 'vim' },
|
||||
{ value: 'emacs', label: 'emacs' },
|
||||
{ value: 'sublime', label: 'sublime' },
|
||||
]
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
initCodeMirror(codeContent) {
|
||||
|
@@ -1,8 +1,10 @@
|
||||
export const iconTypeList = [
|
||||
import i18n from '@/lang'
|
||||
|
||||
export const iconTypeList = () => [
|
||||
// { value: '0', label: '常用' },
|
||||
{ value: '1', label: '线性' },
|
||||
{ value: '2', label: '实底' },
|
||||
{ value: '3', label: '多色' }
|
||||
{ value: '1', label: i18n.t('customIconSelect.outlined') },
|
||||
{ value: '2', label: i18n.t('customIconSelect.filled') },
|
||||
{ value: '3', label: i18n.t('customIconSelect.multicolor') }
|
||||
]
|
||||
|
||||
export const commonIconList = ['changyong-ubuntu',
|
||||
|
@@ -16,7 +16,7 @@
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div :class="`${currentIconType === '4' ? 'selected' : ''}`" @click="handleChangeIconType('4')">
|
||||
自定义
|
||||
{{ this.$t('customIconSelect.custom') }}
|
||||
</div>
|
||||
<a-upload
|
||||
slot="description"
|
||||
@@ -26,7 +26,7 @@
|
||||
accept=".svg,.png,.jpg,.jpeg"
|
||||
v-if="currentIconType === '4'"
|
||||
>
|
||||
<a-button icon="plus" size="small" type="primary">添加</a-button>
|
||||
<a-button icon="plus" size="small" type="primary">{{ $t('add') }}</a-button>
|
||||
</a-upload>
|
||||
</div>
|
||||
<div class="custom-icon-select-popover-content">
|
||||
@@ -55,11 +55,11 @@
|
||||
@click="clickCustomIcon(icon)"
|
||||
>
|
||||
<div class="custom-icon-select-popover-content-img-box">
|
||||
<img :src="`/api/common-setting/v1/file/${icon.data.url}`" />
|
||||
<img v-if="icon.data && icon.data.url" :src="`/api/common-setting/v1/file/${icon.data.url}`" />
|
||||
<a-popconfirm
|
||||
overlayClassName="custom-icon-select-confirm-popover"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
title="确认删除?"
|
||||
:title="$t('confirmDelete')"
|
||||
@confirm="(e) => deleteIcon(e, icon)"
|
||||
@cancel="
|
||||
(e) => {
|
||||
@@ -92,7 +92,7 @@
|
||||
:show-upload-list="false"
|
||||
accept=".svg,.png,.jpg,.jpeg"
|
||||
>
|
||||
<a> 暂无自定义图标,点击此处上传 </a>
|
||||
<a> {{ $t('customIconSelect.nodata') }} </a>
|
||||
</a-upload>
|
||||
</a-empty>
|
||||
</div>
|
||||
@@ -102,27 +102,27 @@
|
||||
</template>
|
||||
<a-form class="custom-icon-select-form" :form="form" v-show="currentIconType === '4' && formVisible">
|
||||
<a-form-item
|
||||
label="名称"
|
||||
:label="$t('name')"
|
||||
:labelCol="{ span: 4 }"
|
||||
:wrapperCol="{ span: 16 }"
|
||||
><a-input
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入名称' }] }]"
|
||||
v-decorator="['name', { rules: [{ required: true, message: $t('placeholder1') }] }]"
|
||||
/></a-form-item>
|
||||
<a-form-item label="预览" :labelCol="{ span: 4 }">
|
||||
<a-form-item :label="$t('customIconSelect.preview')" :labelCol="{ span: 4 }">
|
||||
<div class="custom-icon-select-form-img">
|
||||
<img :src="formImg" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :labelCol="{ span: 16 }">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleCancel">取消</a-button>
|
||||
<a-button size="small" type="primary" @click="handleOk">确定</a-button>
|
||||
<a-button size="small" @click="handleCancel">{{ $t('cancel') }}</a-button>
|
||||
<a-button size="small" type="primary" @click="handleOk">{{ $t('confirm') }}</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<div class="custom-icon-select-block" id="custom-icon-select-block" @click="showSelect">
|
||||
<div class="custom-icon-select-block" :id="`custom-icon-select-block-${uuid}`" @click="showSelect">
|
||||
<img v-if="value.id && value.url" :src="`/api/common-setting/v1/file/${value.url}`" />
|
||||
<ops-icon
|
||||
v-else
|
||||
@@ -134,6 +134,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ColorPicker } from 'element-ui'
|
||||
import {
|
||||
iconTypeList,
|
||||
@@ -166,7 +167,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
iconTypeList,
|
||||
commonIconList,
|
||||
linearIconList,
|
||||
fillIconList,
|
||||
@@ -177,6 +177,7 @@ export default {
|
||||
formVisible: false,
|
||||
formImg: null,
|
||||
file: null,
|
||||
uuid: uuidv4(),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -200,6 +201,9 @@ export default {
|
||||
const splitFileName = this.file.name.split('.')
|
||||
return splitFileName.splice(0, splitFileName.length - 1).join('')
|
||||
},
|
||||
iconTypeList() {
|
||||
return iconTypeList()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.eventListener)
|
||||
@@ -217,7 +221,7 @@ export default {
|
||||
eventListener(e) {
|
||||
if (this.visible) {
|
||||
const dom = document.getElementById(`custom-icon-select-popover`)
|
||||
const dom_icon = document.getElementById(`custom-icon-select-block`)
|
||||
const dom_icon = document.getElementById(`custom-icon-select-block-${this.uuid}`)
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (dom) {
|
||||
@@ -249,12 +253,11 @@ export default {
|
||||
color: '',
|
||||
})
|
||||
} else {
|
||||
this.$emit('change', { name: icon.data.name, id: icon.id, url: icon.data.url })
|
||||
this.$emit('change', { name: icon.data.name, id: icon.id, url: icon?.data?.url })
|
||||
}
|
||||
},
|
||||
showSelect() {
|
||||
this.visible = true
|
||||
console.log(this.value)
|
||||
if (!this.value.name) {
|
||||
this.currentIconType = '3'
|
||||
return
|
||||
@@ -278,7 +281,7 @@ export default {
|
||||
beforeUpload(file) {
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
if (!isLt2M) {
|
||||
this.$message.error('图片大小不可超过2MB!')
|
||||
this.$message.error(this.$t('customIconSelect.sizeLimit'))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -306,7 +309,7 @@ export default {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
addFileData('ops-custom-icon', { data: { name: values.name, url: res.file_name } }).then(() => {
|
||||
this.$message.success('上传成功!')
|
||||
this.$message.success(this.$t('uploadSuccess'))
|
||||
this.handleCancel()
|
||||
this.getFileData()
|
||||
})
|
||||
@@ -318,7 +321,7 @@ export default {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
deleteFileData('ops-custom-icon', icon.id).then(() => {
|
||||
this.$message.success('删除成功!')
|
||||
this.$message.success(this.$t('deleteSuccess'))
|
||||
this.handleCancel()
|
||||
this.getFileData()
|
||||
})
|
||||
|
@@ -6,17 +6,17 @@
|
||||
:flat="true"
|
||||
:multiple="true"
|
||||
:options="employeeTreeSelectOption"
|
||||
placeholder="请输入搜索内容"
|
||||
:placeholder="$t('placeholderSearch')"
|
||||
v-model="treeValue"
|
||||
:max-height="height - 50"
|
||||
noChildrenText="空"
|
||||
noOptionsText="空"
|
||||
:clearable="false"
|
||||
:always-open="true"
|
||||
:default-expand-level="1"
|
||||
:default-expand-level="showInternship ? 0 : 1"
|
||||
:class="{ 'employee-transfer': true, 'employee-transfer-has-input': !!inputValue }"
|
||||
@search-change="changeInputValue"
|
||||
noResultsText="暂无数据"
|
||||
:noResultsText="$t('noData')"
|
||||
openDirection="below"
|
||||
>
|
||||
</treeselect>
|
||||
@@ -85,6 +85,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showInternship: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -99,13 +103,22 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
employeeTreeSelectOption() {
|
||||
return formatOption(
|
||||
const formatOptions = formatOption(
|
||||
this.allTreeDepAndEmp,
|
||||
2,
|
||||
this.isDisabledAllCompany,
|
||||
this.uniqueKey || 'department_id',
|
||||
this.uniqueKey || 'employee_id'
|
||||
)
|
||||
if (this.showInternship) {
|
||||
formatOptions.push(
|
||||
...[
|
||||
{ id: -2, label: '全职' },
|
||||
{ id: -3, label: '实习生' },
|
||||
]
|
||||
)
|
||||
}
|
||||
return formatOptions
|
||||
},
|
||||
allTreeDepAndEmp() {
|
||||
if (this.getDataBySelf) {
|
||||
@@ -148,11 +161,15 @@ export default {
|
||||
const department = []
|
||||
const user = []
|
||||
this.rightData.forEach((item) => {
|
||||
const _split = item.split('-')
|
||||
if (_split[0] === 'department') {
|
||||
department.push(Number(_split[1]))
|
||||
if (item === -2 || item === -3) {
|
||||
department.push(item)
|
||||
} else {
|
||||
user.push(Number(_split[1]))
|
||||
const _split = item.split('-')
|
||||
if (_split[0] === 'department') {
|
||||
department.push(Number(_split[1]))
|
||||
} else {
|
||||
user.push(Number(_split[1]))
|
||||
}
|
||||
}
|
||||
})
|
||||
const _idx = department.findIndex((item) => item === 0)
|
||||
@@ -191,6 +208,12 @@ export default {
|
||||
}
|
||||
},
|
||||
getLabel(id) {
|
||||
if (id === -2) {
|
||||
return '全职'
|
||||
}
|
||||
if (id === -3) {
|
||||
return '实习生'
|
||||
}
|
||||
const _split = id.split('-')
|
||||
const type = _split[0]
|
||||
const _id = Number(_split[1])
|
||||
|
@@ -81,21 +81,23 @@ export default {
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
// 取消订阅
|
||||
cancelAttributes(e, menu) {
|
||||
const that = this
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: `确认取消订阅 ${menu.meta.title}?`,
|
||||
title: this.$t('alert'),
|
||||
content: this.$t('cmdb.preference.confirmcancelSub2', { name: menu.meta.title }),
|
||||
onOk() {
|
||||
const citypeId = menu.meta.typeId
|
||||
const unsubCIType = subscribeCIType(citypeId, '')
|
||||
const unsubTree = subscribeTreeView(citypeId, '')
|
||||
Promise.all([unsubCIType, unsubTree]).then(() => {
|
||||
that.$message.success('取消订阅成功')
|
||||
// 删除路由
|
||||
that.$message.success(that.$t('cmdb.preference.cancelSubSuccess'))
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (Number(citypeId) === Number(lastTypeId)) {
|
||||
localStorage.setItem('ops_ci_typeid', '')
|
||||
}
|
||||
const href = window.location.href
|
||||
const hrefSplit = href.split('/')
|
||||
if (Number(hrefSplit[hrefSplit.length - 1]) === Number(citypeId)) {
|
||||
@@ -115,12 +117,10 @@ export default {
|
||||
},
|
||||
// select menu item
|
||||
onOpenChange(openKeys) {
|
||||
// 在水平模式下时执行,并且不再执行后续
|
||||
if (this.mode === 'horizontal') {
|
||||
this.openKeys = openKeys
|
||||
return
|
||||
}
|
||||
// 非水平模式时
|
||||
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
|
||||
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
|
||||
this.openKeys = openKeys
|
||||
@@ -157,6 +157,12 @@ export default {
|
||||
}
|
||||
return null
|
||||
},
|
||||
renderI18n(title) {
|
||||
if (Object.prototype.toString.call(this.$t(`${title}`)) === '[object Object]') {
|
||||
return title
|
||||
}
|
||||
return this.$t(`${title}`)
|
||||
},
|
||||
renderMenuItem(menu) {
|
||||
const isShowDot = menu.path.substr(0, 22) === '/cmdb/instances/types/'
|
||||
const isShowGrant = menu.path.substr(0, 20) === '/cmdb/relationviews/'
|
||||
@@ -166,9 +172,6 @@ export default {
|
||||
const attrs = { href: menu.meta.targetHref || menu.path, target: menu.meta.target }
|
||||
|
||||
if (menu.children && menu.hideChildrenInMenu) {
|
||||
// 把有子菜单的 并且 父菜单是要隐藏子菜单的
|
||||
// 都给子菜单增加一个 hidden 属性
|
||||
// 用来给刷新页面时, selectedKeys 做控制用
|
||||
menu.children.forEach(item => {
|
||||
item.meta = Object.assign(item.meta, { hidden: true })
|
||||
})
|
||||
@@ -179,7 +182,7 @@ export default {
|
||||
<tag {...{ props, attrs }}>
|
||||
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
|
||||
<span>
|
||||
<span class={menu.meta.title.length > 10 ? 'scroll' : ''}>{menu.meta.title}</span>
|
||||
<span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||
{isShowDot &&
|
||||
<a-popover
|
||||
overlayClassName="custom-menu-extra-submenu"
|
||||
@@ -189,8 +192,8 @@ export default {
|
||||
getPopupContainer={(trigger) => trigger}
|
||||
content={() =>
|
||||
<div>
|
||||
<div onClick={e => this.handlePerm(e, menu, 'CIType')} class="custom-menu-extra-submenu-item"><a-icon type="user-add" />授权</div>
|
||||
<div onClick={e => this.cancelAttributes(e, menu)} class="custom-menu-extra-submenu-item"><a-icon type="star" />取消订阅</div>
|
||||
<div onClick={e => this.handlePerm(e, menu, 'CIType')} class="custom-menu-extra-submenu-item"><a-icon type="user-add" />{ this.renderI18n('grant') }</div>
|
||||
<div onClick={e => this.cancelAttributes(e, menu)} class="custom-menu-extra-submenu-item"><a-icon type="star" />{ this.renderI18n('cmdb.preference.cancelSub') }</div>
|
||||
</div>}
|
||||
>
|
||||
<a-icon type="menu" ref="extraEllipsis" class="custom-menu-extra-ellipsis"></a-icon>
|
||||
@@ -213,7 +216,7 @@ export default {
|
||||
<SubMenu {...{ key: menu.path }}>
|
||||
<span slot="title">
|
||||
{this.renderIcon({ icon: menu.meta.icon, selectedIcon: menu.meta.selectedIcon, routeName: menu.name })}
|
||||
<span>{menu.meta.title}</span>
|
||||
<span>{this.renderI18n(menu.meta.title)}</span>
|
||||
</span>
|
||||
{itemArr}
|
||||
</SubMenu>
|
||||
@@ -280,7 +283,7 @@ export default {
|
||||
this.$refs.cmdbGrantRelationView.open({ name: menu.meta.name, cmdbGrantType: 'relation_view' })
|
||||
}
|
||||
} else {
|
||||
this.$message.error('权限不足!')
|
||||
this.$message.error(this.$t('noPermission'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@
|
||||
<slot></slot>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div>
|
||||
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>暂无数据</div>
|
||||
<div :style="{ paddingTop: '10px' }">
|
||||
<img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
|
2
cmdb-ui/src/components/Pager/index.js
Normal file
2
cmdb-ui/src/components/Pager/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Pager from './index.vue'
|
||||
export default Pager
|
138
cmdb-ui/src/components/Pager/index.vue
Normal file
138
cmdb-ui/src/components/Pager/index.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button
|
||||
class="left-button"
|
||||
size="small"
|
||||
:disabled="prevIsDisabled"
|
||||
@click="prevPage"
|
||||
><a-icon
|
||||
type="left"
|
||||
/></a-button>
|
||||
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
|
||||
<a-button
|
||||
class="right-button"
|
||||
size="small"
|
||||
:disabled="nextIsDisabled"
|
||||
@click="nextPage"
|
||||
><a-icon
|
||||
type="right"
|
||||
/></a-button>
|
||||
<a-dropdown
|
||||
class="dropdown"
|
||||
size="small"
|
||||
placement="topCenter"
|
||||
:trigger="['click']"
|
||||
:disabled="dropdownIsDisabled"
|
||||
>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size, index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ `${size}${$t('itemsPerPage')}` }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small">{{ `${pageSize}${$t('itemsPerPage')}` }}<a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Pager',
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropdownIsDisabled: false,
|
||||
prevIsDisabled: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nextIsDisabled() {
|
||||
return this.isLoading || this.total < this.pageSize
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isLoading: {
|
||||
immediate: true,
|
||||
handler: function(val) {
|
||||
if (val) {
|
||||
this.dropdownIsDisabled = true
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.dropdownIsDisabled = false
|
||||
if (this.currentPage === 1) {
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.prevIsDisabled = false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
currentPage: {
|
||||
immediate: true,
|
||||
handler: function(val) {
|
||||
if (val === 1) {
|
||||
this.prevIsDisabled = true
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(size) {
|
||||
this.$emit('showSizeChange', size)
|
||||
},
|
||||
nextPage() {
|
||||
const pageNum = this.currentPage + 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
prevPage() {
|
||||
const pageNum = this.currentPage - 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
.left-button {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.right-button {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.page-button {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -3,12 +3,12 @@
|
||||
<a-switch
|
||||
class="role-transfer-switch"
|
||||
v-model="isUserRole"
|
||||
checked-children="用户"
|
||||
un-checked-children="虚拟"
|
||||
:checked-children="$t('user')"
|
||||
:un-checked-children="$t('visual')"
|
||||
@change="loadRoles"
|
||||
/>
|
||||
<div class="role-transfer-left">
|
||||
<a-input placeholder="请输入搜索内容" v-model="searchValue" />
|
||||
<a-input :placeholder="$t('placeholderSearch')" v-model="searchValue" />
|
||||
<div v-for="item in filterAllRoles" :key="item.id" @click="handleSelectedLeft(item.id)">
|
||||
<a-checkbox :checked="selectedLeft.includes(item.id)" />
|
||||
<div :title="item.name" class="role-transfer-left-role">{{ item.name }}</div>
|
||||
|
@@ -10,9 +10,10 @@
|
||||
>
|
||||
<a-icon type="setting" />
|
||||
</span>
|
||||
<span class="locale" @click="changeLang">{{ locale === 'zh' ? 'English' : '中文' }}</span>
|
||||
<a-popover
|
||||
trigger="click"
|
||||
:overlayStyle="{ width: '120px' }"
|
||||
:overlayStyle="{ width: '150px' }"
|
||||
placement="bottomRight"
|
||||
overlayClassName="custom-user"
|
||||
>
|
||||
@@ -20,12 +21,12 @@
|
||||
<router-link :to="{ name: 'setting_person' }" :style="{ color: '#000000a6' }">
|
||||
<div class="custom-user-item">
|
||||
<a-icon type="user" :style="{ marginRight: '10px' }" />
|
||||
<span>个人中心</span>
|
||||
<span>{{ $t('topMenu.personalCenter') }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<div @click="handleLogout" class="custom-user-item">
|
||||
<a-icon type="logout" :style="{ marginRight: '10px' }" />
|
||||
<span>退出登录</span>
|
||||
<span>{{ $t('topMenu.logout') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span class="action ant-dropdown-link user-dropdown-menu">
|
||||
@@ -44,8 +45,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions, mapGetters, mapMutations } from 'vuex'
|
||||
import DocumentLink from './DocumentLink.vue'
|
||||
import { mapState, mapActions, mapGetters } from 'vuex'
|
||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||
|
||||
export default {
|
||||
name: 'UserMenu',
|
||||
@@ -53,23 +55,22 @@ export default {
|
||||
DocumentLink,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
...mapState(['user', 'locale']),
|
||||
hasBackendPermission() {
|
||||
return this.user?.roles?.permissions.includes('acl_admin', 'backend_admin') || false
|
||||
return this.user?.detailPermissions?.backend?.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['Logout']),
|
||||
...mapGetters(['nickname', 'avatar']),
|
||||
...mapMutations(['SET_LOCALE']),
|
||||
handleLogout() {
|
||||
const that = this
|
||||
|
||||
this.$confirm({
|
||||
title: '提示',
|
||||
content: '真的要注销登录吗 ?',
|
||||
title: this.$t('tip'),
|
||||
content: this.$t('topMenu.confirmLogout'),
|
||||
onOk() {
|
||||
// localStorage.removeItem('ops_cityps_currentId')
|
||||
localStorage.clear()
|
||||
return that.Logout()
|
||||
},
|
||||
onCancel() {},
|
||||
@@ -78,9 +79,22 @@ export default {
|
||||
handleClick() {
|
||||
this.$router.push('/setting')
|
||||
},
|
||||
changeLang() {
|
||||
if (this.locale === 'zh') {
|
||||
this.SET_LOCALE('en')
|
||||
this.$i18n.locale = 'en'
|
||||
} else {
|
||||
this.SET_LOCALE('zh')
|
||||
this.$i18n.locale = 'zh'
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
setDocumentTitle(`${this.$t(this.$route.meta.title)} - ${domTitle}`)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.color {
|
||||
@@ -100,4 +114,11 @@ export default {
|
||||
color: #000000a6;
|
||||
}
|
||||
}
|
||||
|
||||
.locale {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,7 +2,6 @@ const appConfig = {
|
||||
buildModules: ['cmdb', 'acl'], // 需要编译的模块
|
||||
redirectTo: '/cmdb', // 首页的重定向路径
|
||||
buildAclToModules: true, // 是否在各个应用下 内联权限管理
|
||||
ssoLogoutURL: '/api/sso/logout',
|
||||
showDocs: false,
|
||||
useEncryption: false,
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* 项目默认配置项
|
||||
* useSSO - 是否启用单点登录, 默认为否, 可以根据需要接入到公司的单点登录系统
|
||||
* primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage
|
||||
* navTheme - sidebar theme ['dark', 'light'] 两种主题
|
||||
* colorWeak - 色盲模式
|
||||
@@ -15,8 +14,6 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
useSSO: false,
|
||||
ssoLoginUrl: '/api/sso/login',
|
||||
primaryColor: '#1890ff', // primary color of ant design
|
||||
navTheme: 'dark', // theme for nav menu
|
||||
layout: 'sidemenu', // nav menu position: sidemenu or topmenu
|
||||
|
@@ -38,11 +38,18 @@ import CardTitle from '@/components/CardTitle'
|
||||
import ElementUI from 'element-ui'
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import OpsTable from '@/components/OpsTable'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import i18n from '@/lang'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$bus = EventBus
|
||||
|
||||
VXETable.setup({
|
||||
i18n: (key, args) => i18n.t(key, args)
|
||||
})
|
||||
Vue.use(VXETable)
|
||||
VXETable.use(VXETablePluginExportXLSX)
|
||||
Vue.use(VueI18n)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@@ -75,4 +82,3 @@ Vue.component('CustomRadio', CustomRadio)
|
||||
Vue.component('CardTitle', CardTitle)
|
||||
Vue.component('Treeselect', Treeselect)
|
||||
Vue.component('OpsTable', OpsTable)
|
||||
|
||||
|
@@ -6,8 +6,8 @@ import store from './store'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||
import config from '@/config/setting'
|
||||
import { ACCESS_TOKEN } from './store/global/mutation-types'
|
||||
import i18n from '@/lang'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
@@ -16,16 +16,16 @@ const whitePath = ['/user/login', '/user/logout', '/user/register', '/api/sso/lo
|
||||
|
||||
// 此处不处理登录, 只处理 是否有用户信息的认证 前端permission的处理 axios处理401 -> 登录
|
||||
// 登录页面处理处理 是否使用单点登录
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start() // start progress bar
|
||||
to.meta && (!!to.meta.title && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
|
||||
to.meta && (!!to.meta.title && setDocumentTitle(`${i18n.t(to.meta.title)} - ${domTitle}`))
|
||||
|
||||
const authed = store.state.authed
|
||||
|
||||
|
||||
const auth_type = localStorage.getItem('ops_auth_type')
|
||||
if (whitePath.includes(to.path)) {
|
||||
next()
|
||||
} else if ((config.useSSO || (!config.useSSO && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) {
|
||||
} else if ((auth_type || (!auth_type && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) {
|
||||
store.dispatch('GetAuthDataEnable')
|
||||
store.dispatch('GetInfo').then(res => {
|
||||
const roles = res.result && res.result.role
|
||||
store.dispatch("loadAllUsers")
|
||||
@@ -46,10 +46,17 @@ router.beforeEach((to, from, next) => {
|
||||
}).catch((e) => {
|
||||
setTimeout(() => { store.dispatch('Logout') }, 3000)
|
||||
})
|
||||
} else if (to.path === '/user/login' && !config.useSSO && store.getters.roles.length !== 0) {
|
||||
} else if (to.path === '/user/login' && !auth_type && store.getters.roles.length !== 0) {
|
||||
next({ path: '/' })
|
||||
} else if (!config.useSSO && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') {
|
||||
next({ path: '/user/login', query: { redirect: to.fullPath } })
|
||||
} else if (!auth_type && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') {
|
||||
await store.dispatch('GetAuthDataEnable')
|
||||
const { enable_list = [] } = store?.state?.user?.auth_enable ?? {}
|
||||
const _enable_list = enable_list.filter(en => en.auth_type !== 'LDAP')
|
||||
if (_enable_list.length === 1) {
|
||||
next({ path: '/user/logout', query: { redirect: to.fullPath } })
|
||||
} else {
|
||||
next({ path: '/user/login', query: { redirect: to.fullPath } })
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
|
151
cmdb-ui/src/lang/en.js
Normal file
151
cmdb-ui/src/lang/en.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import cmdb_en from '@/modules/cmdb/lang/en.js'
|
||||
import cs_en from '../views/setting/lang/en.js'
|
||||
import acl_en from '@/modules/acl/lang/en.js'
|
||||
|
||||
export default {
|
||||
commonMenu: {
|
||||
permission: 'Permission',
|
||||
role: 'Roles',
|
||||
resource: 'Resources',
|
||||
resourceType: 'Resource Types',
|
||||
trigger: 'Triggers',
|
||||
},
|
||||
screen: 'Big Screen',
|
||||
dashboard: 'Dashboard',
|
||||
admin: 'Admin',
|
||||
user: 'User',
|
||||
role: 'Role',
|
||||
operation: 'Operation',
|
||||
login: 'Login',
|
||||
refresh: 'Refresh',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
create: 'Create',
|
||||
edit: 'Edit',
|
||||
deleting: 'Deleting',
|
||||
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
|
||||
grant: 'Grant',
|
||||
login_at: 'Login At',
|
||||
logout_at: 'Logout At',
|
||||
createSuccess: 'Create Success',
|
||||
editSuccess: 'edit Success',
|
||||
warning: 'Warning',
|
||||
export: 'Export',
|
||||
placeholderSearch: 'Please Search',
|
||||
success: 'Success',
|
||||
fail: 'Fail',
|
||||
browser: 'Browser',
|
||||
status: 'Status',
|
||||
type: 'Type',
|
||||
description: 'Description',
|
||||
new: 'New',
|
||||
add: 'Add',
|
||||
define: 'Define',
|
||||
update: 'Update',
|
||||
clear: 'Clear',
|
||||
delete: 'Delete',
|
||||
copy: 'Copy',
|
||||
created_at: 'Created At',
|
||||
updated_at: 'Updated At',
|
||||
placeholder1: 'Please Input',
|
||||
placeholder2: 'Please Select',
|
||||
confirmDelete: 'Confirm delete?',
|
||||
confirmDelete2: 'Confirm delete [{name}]?',
|
||||
query: 'Query',
|
||||
search: 'Search',
|
||||
hide: 'Hide',
|
||||
expand: 'Expand',
|
||||
save: 'Save',
|
||||
submit: 'Submit',
|
||||
upload: 'Import',
|
||||
download: 'Export',
|
||||
name: 'Name',
|
||||
alias: 'Alias',
|
||||
desc: 'Description',
|
||||
other: 'Other',
|
||||
icon: 'Icon',
|
||||
addSuccess: 'Added successfully',
|
||||
uploadSuccess: 'Import successfully',
|
||||
saveSuccess: 'Save successfully',
|
||||
copySuccess: 'Copy successfully',
|
||||
updateSuccess: 'Updated successfully',
|
||||
deleteSuccess: 'Deleted successfully',
|
||||
operateSuccess: 'The operation was successful',
|
||||
noPermission: 'No Permission',
|
||||
noData: 'No Data',
|
||||
seconds: 'Seconds',
|
||||
createdAt: 'Created At',
|
||||
updatedAt: 'Updated At',
|
||||
deletedAt: 'Deleted At',
|
||||
required: 'required',
|
||||
email: 'Email',
|
||||
wechat: 'Wechat',
|
||||
dingding: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
bot: 'Robot',
|
||||
checkAll: 'Select All',
|
||||
loading: 'Loading...',
|
||||
view: 'View',
|
||||
reset: 'Reset',
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
all: 'All',
|
||||
selectRows: 'Selected: {rows} items',
|
||||
itemsPerPage: '/page',
|
||||
'星期一': 'Monday',
|
||||
'星期二': 'Tuesday',
|
||||
'星期三': 'Wednesday',
|
||||
'星期四': 'Thursday',
|
||||
'星期五': 'Friday',
|
||||
'星期六': 'Saturday',
|
||||
'星期日': 'Sunday',
|
||||
hour: 'hour',
|
||||
'items/page': '{items} items/page',
|
||||
max: 'Max',
|
||||
min: 'Min',
|
||||
visual: 'Visual',
|
||||
default: 'default',
|
||||
tip: 'Tip',
|
||||
pagination: {
|
||||
total: '{range0}-{range1} of {total} items'
|
||||
},
|
||||
topMenu: {
|
||||
personalCenter: 'My Profile',
|
||||
logout: 'Logout',
|
||||
confirmLogout: 'Are you sure to log out?'
|
||||
},
|
||||
cmdbFilterComp: {
|
||||
conditionFilter: 'Conditional filtering',
|
||||
and: 'and',
|
||||
or: 'or',
|
||||
is: 'equal',
|
||||
'~is': 'not equal',
|
||||
contain: 'contain',
|
||||
'~contain': 'not contain',
|
||||
start_with: 'start_with',
|
||||
'~start_with': 'not start_with',
|
||||
end_with: 'end_with',
|
||||
'~end_with': 'not end_with',
|
||||
'~value': 'null',
|
||||
value: 'not null',
|
||||
in: 'in',
|
||||
'~in': 'not in',
|
||||
range: 'range',
|
||||
'~range': 'out of range',
|
||||
compare: 'compare',
|
||||
addHere: 'Add Here',
|
||||
split: 'split by {separator}'
|
||||
},
|
||||
customIconSelect: {
|
||||
outlined: 'Outlined',
|
||||
filled: 'Filled',
|
||||
multicolor: 'Multicolor',
|
||||
custom: 'Custom',
|
||||
preview: 'Preview',
|
||||
sizeLimit: 'The image size cannot exceed 2MB!',
|
||||
nodata: 'There are currently no custom icons available. Click here to upload'
|
||||
},
|
||||
cmdb: cmdb_en,
|
||||
cs: cs_en,
|
||||
acl: acl_en,
|
||||
}
|
18
cmdb-ui/src/lang/index.js
Normal file
18
cmdb-ui/src/lang/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import VueI18n from 'vue-i18n'
|
||||
import zh from './zh'
|
||||
import en from './en'
|
||||
import Vue from 'vue'
|
||||
import zhCN from 'vxe-table/lib/locale/lang/zh-CN'
|
||||
import enUS from 'vxe-table/lib/locale/lang/en-US'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
const i18n = new VueI18n({
|
||||
locale: 'zh', // 初始化中文
|
||||
messages: {
|
||||
'zh': { ...zh, ...zhCN },
|
||||
'en': { ...en, ...enUS },
|
||||
},
|
||||
silentTranslationWarn: true
|
||||
})
|
||||
|
||||
export default i18n
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user