mirror of https://github.com/jumpserver/jumpserver
perf: 命令存储ES可根据日期动态建立索引 (#8180)
* perf: 命令存储ES可根据日期动态建立索引 * perf: 优化合并字段 * feat: 修改逻辑pull/8207/head
parent
ab737ae09b
commit
64eda5f28b
|
@ -32,6 +32,10 @@ def local_now_display(fmt='%Y-%m-%d %H:%M:%S'):
|
||||||
return local_now().strftime(fmt)
|
return local_now().strftime(fmt)
|
||||||
|
|
||||||
|
|
||||||
|
def local_now_date_display(fmt='%Y-%m-%d'):
|
||||||
|
return local_now().strftime(fmt)
|
||||||
|
|
||||||
|
|
||||||
_rest_dt_field = DateTimeField()
|
_rest_dt_field = DateTimeField()
|
||||||
dt_parser = _rest_dt_field.to_internal_value
|
dt_parser = _rest_dt_field.to_internal_value
|
||||||
dt_formatter = _rest_dt_field.to_representation
|
dt_formatter = _rest_dt_field.to_representation
|
||||||
|
|
|
@ -30,7 +30,7 @@ msgstr "Acls"
|
||||||
#: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29
|
#: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29
|
||||||
#: settings/models.py:29 settings/serializers/sms.py:6
|
#: settings/models.py:29 settings/serializers/sms.py:6
|
||||||
#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58
|
#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58
|
||||||
#: terminal/models/storage.py:23 terminal/models/task.py:16
|
#: terminal/models/storage.py:24 terminal/models/task.py:16
|
||||||
#: terminal/models/terminal.py:100 users/forms/profile.py:32
|
#: terminal/models/terminal.py:100 users/forms/profile.py:32
|
||||||
#: users/models/group.py:15 users/models/user.py:661
|
#: users/models/group.py:15 users/models/user.py:661
|
||||||
#: xpack/plugins/cloud/models.py:28
|
#: xpack/plugins/cloud/models.py:28
|
||||||
|
@ -62,7 +62,7 @@ msgstr "アクティブ"
|
||||||
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68
|
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68
|
||||||
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
||||||
#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68
|
#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68
|
||||||
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
|
#: terminal/models/storage.py:27 terminal/models/terminal.py:114
|
||||||
#: tickets/models/comment.py:24 tickets/models/ticket.py:154
|
#: tickets/models/comment.py:24 tickets/models/ticket.py:154
|
||||||
#: users/models/group.py:16 users/models/user.py:698
|
#: users/models/group.py:16 users/models/user.py:698
|
||||||
#: xpack/plugins/change_auth_plan/models/base.py:44
|
#: xpack/plugins/change_auth_plan/models/base.py:44
|
||||||
|
@ -305,7 +305,7 @@ msgstr "カテゴリ"
|
||||||
#: assets/models/cmd_filter.py:82 assets/models/user.py:246
|
#: assets/models/cmd_filter.py:82 assets/models/user.py:246
|
||||||
#: perms/models/application_permission.py:24
|
#: perms/models/application_permission.py:24
|
||||||
#: perms/serializers/application/user_permission.py:34
|
#: perms/serializers/application/user_permission.py:34
|
||||||
#: terminal/models/storage.py:55 terminal/models/storage.py:119
|
#: terminal/models/storage.py:56 terminal/models/storage.py:136
|
||||||
#: tickets/models/flow.py:56 tickets/models/ticket.py:131
|
#: tickets/models/flow.py:56 tickets/models/ticket.py:131
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:28
|
#: xpack/plugins/change_auth_plan/models/app.py:28
|
||||||
|
@ -4639,7 +4639,7 @@ msgstr "オンラインセッションを持つ"
|
||||||
msgid "Terminals"
|
msgid "Terminals"
|
||||||
msgstr "ターミナル管理"
|
msgstr "ターミナル管理"
|
||||||
|
|
||||||
#: terminal/backends/command/es.py:26
|
#: terminal/backends/command/es.py:28
|
||||||
msgid "Invalid elasticsearch config"
|
msgid "Invalid elasticsearch config"
|
||||||
msgstr "無効なElasticsearch構成"
|
msgstr "無効なElasticsearch構成"
|
||||||
|
|
||||||
|
@ -4883,15 +4883,15 @@ msgstr "スレッド"
|
||||||
msgid "Boot Time"
|
msgid "Boot Time"
|
||||||
msgstr "ブート時間"
|
msgstr "ブート時間"
|
||||||
|
|
||||||
#: terminal/models/storage.py:25
|
#: terminal/models/storage.py:26
|
||||||
msgid "Default storage"
|
msgid "Default storage"
|
||||||
msgstr "デフォルトのストレージ"
|
msgstr "デフォルトのストレージ"
|
||||||
|
|
||||||
#: terminal/models/storage.py:113 terminal/models/terminal.py:108
|
#: terminal/models/storage.py:130 terminal/models/terminal.py:108
|
||||||
msgid "Command storage"
|
msgid "Command storage"
|
||||||
msgstr "コマンドストレージ"
|
msgstr "コマンドストレージ"
|
||||||
|
|
||||||
#: terminal/models/storage.py:173 terminal/models/terminal.py:109
|
#: terminal/models/storage.py:190 terminal/models/terminal.py:109
|
||||||
msgid "Replay storage"
|
msgid "Replay storage"
|
||||||
msgstr "再生ストレージ"
|
msgstr "再生ストレージ"
|
||||||
|
|
||||||
|
@ -5008,14 +5008,26 @@ msgid "Port invalid"
|
||||||
msgstr "ポートが無効"
|
msgstr "ポートが無効"
|
||||||
|
|
||||||
#: terminal/serializers/storage.py:159
|
#: terminal/serializers/storage.py:159
|
||||||
|
msgid "Index by date"
|
||||||
|
msgstr "日付による索引付け"
|
||||||
|
|
||||||
|
#: terminal/serializers/storage.py:160
|
||||||
|
msgid "Whether to create an index by date"
|
||||||
|
msgstr "現在の日付に基づいてインデックスを動的に作成するかどうか"
|
||||||
|
|
||||||
|
#: terminal/serializers/storage.py:163
|
||||||
|
msgid "Index prefix"
|
||||||
|
msgstr "インデックス接頭辞"
|
||||||
|
|
||||||
|
#: terminal/serializers/storage.py:166
|
||||||
msgid "Index"
|
msgid "Index"
|
||||||
msgstr "インデックス"
|
msgstr "インデックス"
|
||||||
|
|
||||||
#: terminal/serializers/storage.py:161
|
#: terminal/serializers/storage.py:168
|
||||||
msgid "Doc type"
|
msgid "Doc type"
|
||||||
msgstr "Docタイプ"
|
msgstr "Docタイプ"
|
||||||
|
|
||||||
#: terminal/serializers/storage.py:163
|
#: terminal/serializers/storage.py:170
|
||||||
msgid "Ignore Certificate Verification"
|
msgid "Ignore Certificate Verification"
|
||||||
msgstr "証明書の検証を無視する"
|
msgstr "証明書の検証を無視する"
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ msgstr "访问控制"
|
||||||
#: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29
|
#: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29
|
||||||
#: settings/models.py:29 settings/serializers/sms.py:6
|
#: settings/models.py:29 settings/serializers/sms.py:6
|
||||||
#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58
|
#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58
|
||||||
#: terminal/models/storage.py:23 terminal/models/task.py:16
|
#: terminal/models/storage.py:24 terminal/models/task.py:16
|
||||||
#: terminal/models/terminal.py:100 users/forms/profile.py:32
|
#: terminal/models/terminal.py:100 users/forms/profile.py:32
|
||||||
#: users/models/group.py:15 users/models/user.py:661
|
#: users/models/group.py:15 users/models/user.py:661
|
||||||
#: xpack/plugins/cloud/models.py:28
|
#: xpack/plugins/cloud/models.py:28
|
||||||
|
@ -61,7 +61,7 @@ msgstr "激活中"
|
||||||
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68
|
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68
|
||||||
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
||||||
#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68
|
#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68
|
||||||
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
|
#: terminal/models/storage.py:27 terminal/models/terminal.py:114
|
||||||
#: tickets/models/comment.py:24 tickets/models/ticket.py:154
|
#: tickets/models/comment.py:24 tickets/models/ticket.py:154
|
||||||
#: users/models/group.py:16 users/models/user.py:698
|
#: users/models/group.py:16 users/models/user.py:698
|
||||||
#: xpack/plugins/change_auth_plan/models/base.py:44
|
#: xpack/plugins/change_auth_plan/models/base.py:44
|
||||||
|
@ -300,7 +300,7 @@ msgstr "类别"
|
||||||
#: assets/models/cmd_filter.py:82 assets/models/user.py:246
|
#: assets/models/cmd_filter.py:82 assets/models/user.py:246
|
||||||
#: perms/models/application_permission.py:24
|
#: perms/models/application_permission.py:24
|
||||||
#: perms/serializers/application/user_permission.py:34
|
#: perms/serializers/application/user_permission.py:34
|
||||||
#: terminal/models/storage.py:55 terminal/models/storage.py:119
|
#: terminal/models/storage.py:56 terminal/models/storage.py:136
|
||||||
#: tickets/models/flow.py:56 tickets/models/ticket.py:131
|
#: tickets/models/flow.py:56 tickets/models/ticket.py:131
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:28
|
#: xpack/plugins/change_auth_plan/models/app.py:28
|
||||||
|
@ -4565,7 +4565,7 @@ msgstr "有在线会话"
|
||||||
msgid "Terminals"
|
msgid "Terminals"
|
||||||
msgstr "终端管理"
|
msgstr "终端管理"
|
||||||
|
|
||||||
#: terminal/backends/command/es.py:26
|
#: terminal/backends/command/es.py:28
|
||||||
msgid "Invalid elasticsearch config"
|
msgid "Invalid elasticsearch config"
|
||||||
msgstr "无效的 Elasticsearch 配置"
|
msgstr "无效的 Elasticsearch 配置"
|
||||||
|
|
||||||
|
@ -4809,15 +4809,15 @@ msgstr "线程数"
|
||||||
msgid "Boot Time"
|
msgid "Boot Time"
|
||||||
msgstr "运行时间"
|
msgstr "运行时间"
|
||||||
|
|
||||||
#: terminal/models/storage.py:25
|
#: terminal/models/storage.py:26
|
||||||
msgid "Default storage"
|
msgid "Default storage"
|
||||||
msgstr "默认存储"
|
msgstr "默认存储"
|
||||||
|
|
||||||
#: terminal/models/storage.py:113 terminal/models/terminal.py:108
|
#: terminal/models/storage.py:130 terminal/models/terminal.py:108
|
||||||
msgid "Command storage"
|
msgid "Command storage"
|
||||||
msgstr "命令存储"
|
msgstr "命令存储"
|
||||||
|
|
||||||
#: terminal/models/storage.py:173 terminal/models/terminal.py:109
|
#: terminal/models/storage.py:190 terminal/models/terminal.py:109
|
||||||
msgid "Replay storage"
|
msgid "Replay storage"
|
||||||
msgstr "录像存储"
|
msgstr "录像存储"
|
||||||
|
|
||||||
|
@ -4934,14 +4934,26 @@ msgid "Port invalid"
|
||||||
msgstr "端口无效"
|
msgstr "端口无效"
|
||||||
|
|
||||||
#: terminal/serializers/storage.py:159
|
#: terminal/serializers/storage.py:159
|
||||||
|
msgid "Index by date"
|
||||||
|
msgstr "按日期建索引"
|
||||||
|
|
||||||
|
#: terminal/serializers/storage.py:160
|
||||||
|
msgid "Whether to create an index by date"
|
||||||
|
msgstr "是否根据日期动态建立索引"
|
||||||
|
|
||||||
|
#: terminal/serializers/storage.py:163
|
||||||
|
msgid "Index prefix"
|
||||||
|
msgstr "索引前缀"
|
||||||
|
|
||||||
|
#: terminal/serializers/storage.py:166
|
||||||
msgid "Index"
|
msgid "Index"
|
||||||
msgstr "索引"
|
msgstr "索引"
|
||||||
|
|
||||||
#: terminal/serializers/storage.py:161
|
#: terminal/serializers/storage.py:168
|
||||||
msgid "Doc type"
|
msgid "Doc type"
|
||||||
msgstr "文档类型"
|
msgstr "文档类型"
|
||||||
|
|
||||||
#: terminal/serializers/storage.py:163
|
#: terminal/serializers/storage.py:170
|
||||||
msgid "Ignore Certificate Verification"
|
msgid "Ignore Certificate Verification"
|
||||||
msgstr "忽略证书认证"
|
msgstr "忽略证书认证"
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import pytz
|
||||||
|
import inspect
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import reduce, partial
|
from functools import reduce, partial
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
import pytz
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
import inspect
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.db.models import QuerySet as DJQuerySet
|
from django.db.models import QuerySet as DJQuerySet
|
||||||
|
@ -15,6 +16,7 @@ from elasticsearch.exceptions import RequestError, NotFoundError
|
||||||
|
|
||||||
from common.utils.common import lazyproperty
|
from common.utils.common import lazyproperty
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from common.utils.timezone import local_now_date_display, utc_now
|
||||||
from common.exceptions import JMSException
|
from common.exceptions import JMSException
|
||||||
from .models import AbstractSessionCommand
|
from .models import AbstractSessionCommand
|
||||||
|
|
||||||
|
@ -28,12 +30,13 @@ class InvalidElasticsearch(JMSException):
|
||||||
|
|
||||||
class CommandStore(object):
|
class CommandStore(object):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
hosts = config.get("HOSTS")
|
|
||||||
kwargs = config.get("OTHER", {})
|
|
||||||
self.index = config.get("INDEX") or 'jumpserver'
|
|
||||||
self.doc_type = config.get("DOC_TYPE") or '_doc'
|
self.doc_type = config.get("DOC_TYPE") or '_doc'
|
||||||
|
self.index_prefix = config.get('INDEX') or 'jumpserver'
|
||||||
|
self.is_index_by_date = bool(config.get('INDEX_BY_DATE'))
|
||||||
self.exact_fields = {}
|
self.exact_fields = {}
|
||||||
self.match_fields = {}
|
self.match_fields = {}
|
||||||
|
hosts = config.get("HOSTS")
|
||||||
|
kwargs = config.get("OTHER", {})
|
||||||
|
|
||||||
ignore_verify_certs = kwargs.pop('IGNORE_VERIFY_CERTS', False)
|
ignore_verify_certs = kwargs.pop('IGNORE_VERIFY_CERTS', False)
|
||||||
if ignore_verify_certs:
|
if ignore_verify_certs:
|
||||||
|
@ -50,6 +53,17 @@ class CommandStore(object):
|
||||||
else:
|
else:
|
||||||
self.match_fields.update(may_exact_fields)
|
self.match_fields.update(may_exact_fields)
|
||||||
|
|
||||||
|
self.init_index(config)
|
||||||
|
|
||||||
|
def init_index(self, config):
|
||||||
|
if self.is_index_by_date:
|
||||||
|
date = local_now_date_display()
|
||||||
|
self.index = '%s-%s' % (self.index_prefix, date)
|
||||||
|
self.query_index = '%s-alias' % self.index_prefix
|
||||||
|
else:
|
||||||
|
self.index = config.get("INDEX") or 'jumpserver'
|
||||||
|
self.query_index = config.get("INDEX") or 'jumpserver'
|
||||||
|
|
||||||
def is_new_index_type(self):
|
def is_new_index_type(self):
|
||||||
if not self.ping(timeout=3):
|
if not self.ping(timeout=3):
|
||||||
return False
|
return False
|
||||||
|
@ -101,11 +115,18 @@ class CommandStore(object):
|
||||||
else:
|
else:
|
||||||
mappings = {'mappings': {'properties': properties}}
|
mappings = {'mappings': {'properties': properties}}
|
||||||
|
|
||||||
|
if self.is_index_by_date:
|
||||||
|
mappings['aliases'] = {
|
||||||
|
self.query_index: {}
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
self.es.indices.create(self.index, body=mappings)
|
self.es.indices.create(self.index, body=mappings)
|
||||||
return
|
return
|
||||||
except RequestError as e:
|
except RequestError as e:
|
||||||
logger.exception(e)
|
if e.error == 'resource_already_exists_exception':
|
||||||
|
logger.warning(e)
|
||||||
|
else:
|
||||||
|
logger.exception(e)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def make_data(command):
|
def make_data(command):
|
||||||
|
@ -141,7 +162,7 @@ class CommandStore(object):
|
||||||
body = self.get_query_body(**query)
|
body = self.get_query_body(**query)
|
||||||
|
|
||||||
data = self.es.search(
|
data = self.es.search(
|
||||||
index=self.index, doc_type=self.doc_type, body=body, from_=from_, size=size,
|
index=self.query_index, doc_type=self.doc_type, body=body, from_=from_, size=size,
|
||||||
sort=sort
|
sort=sort
|
||||||
)
|
)
|
||||||
source_data = []
|
source_data = []
|
||||||
|
@ -154,7 +175,7 @@ class CommandStore(object):
|
||||||
|
|
||||||
def count(self, **query):
|
def count(self, **query):
|
||||||
body = self.get_query_body(**query)
|
body = self.get_query_body(**query)
|
||||||
data = self.es.count(index=self.index, doc_type=self.doc_type, body=body)
|
data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body)
|
||||||
return data["count"]
|
return data["count"]
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
import jms_storage
|
import jms_storage
|
||||||
|
@ -10,6 +11,7 @@ from django.conf import settings
|
||||||
from common.mixins import CommonModelMixin
|
from common.mixins import CommonModelMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.db.fields import EncryptJsonDictTextField
|
from common.db.fields import EncryptJsonDictTextField
|
||||||
|
from common.utils.timezone import local_now_date_display
|
||||||
from terminal.backends import TYPE_ENGINE_MAPPING
|
from terminal.backends import TYPE_ENGINE_MAPPING
|
||||||
from .terminal import Terminal
|
from .terminal import Terminal
|
||||||
from .command import Command
|
from .command import Command
|
||||||
|
@ -63,6 +65,10 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin):
|
||||||
def type_server(self):
|
def type_server(self):
|
||||||
return self.type == const.CommandStorageTypeChoices.server.value
|
return self.type == const.CommandStorageTypeChoices.server.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_es(self):
|
||||||
|
return self.type == const.CommandStorageTypeChoices.es.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_null_or_server(self):
|
def type_null_or_server(self):
|
||||||
return self.type_null or self.type_server
|
return self.type_null or self.type_server
|
||||||
|
@ -73,6 +79,18 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin):
|
||||||
config.update({'TYPE': self.type})
|
config.update({'TYPE': self.type})
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valid_config(self):
|
||||||
|
config = self.config
|
||||||
|
if self.type_es and config.get('INDEX_BY_DATE'):
|
||||||
|
engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type])
|
||||||
|
store = engine_mod.CommandStore(config)
|
||||||
|
store._ensure_index_exists()
|
||||||
|
index_prefix = config.get('INDEX') or 'jumpserver'
|
||||||
|
date = local_now_date_display()
|
||||||
|
config['INDEX'] = '%s-%s' % (index_prefix, date)
|
||||||
|
return config
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
if self.type_null_or_server:
|
if self.type_null_or_server:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -68,7 +68,7 @@ class StorageMixin:
|
||||||
def get_command_storage_config(self):
|
def get_command_storage_config(self):
|
||||||
s = self.get_command_storage()
|
s = self.get_command_storage()
|
||||||
if s:
|
if s:
|
||||||
config = s.config
|
config = s.valid_config
|
||||||
else:
|
else:
|
||||||
config = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
config = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
||||||
return config
|
return config
|
||||||
|
|
|
@ -155,6 +155,10 @@ class CommandStorageTypeESSerializer(serializers.Serializer):
|
||||||
child=serializers.CharField(validators=[command_storage_es_host_format_validator]),
|
child=serializers.CharField(validators=[command_storage_es_host_format_validator]),
|
||||||
label=_('Hosts'), help_text=_(hosts_help_text), allow_null=True
|
label=_('Hosts'), help_text=_(hosts_help_text), allow_null=True
|
||||||
)
|
)
|
||||||
|
INDEX_BY_DATE = serializers.BooleanField(
|
||||||
|
default=False, label=_('Index by date'),
|
||||||
|
help_text=_('Whether to create an index by date')
|
||||||
|
)
|
||||||
INDEX = serializers.CharField(
|
INDEX = serializers.CharField(
|
||||||
max_length=1024, default='jumpserver', label=_('Index'), allow_null=True
|
max_length=1024, default='jumpserver', label=_('Index'), allow_null=True
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue