Commit ddafd7ba authored by 老广's avatar 老广 Committed by BaiJiangJie

Dev ansible windows 2 (#2783)

* [Update] 改密支持windows

* [Update] 修改asset表结构

* [Feature] Windows支持批量改密、测试可连接性等功能

* [Update] 处理创建资产时labels的问题

* [Update] 优化测试管理系统、系统用户可连接性任务执行逻辑

* [Update] 优化ansible任务逻辑;添加自动推送rdp系统用户功能

* [Update] 添加翻译

* [Update] 优化ansible任务逻辑(测试系统用户可连接性, 通过协议过滤资产)

* [Update] 更新翻译

* [Update] 更新翻译

* [Update] 推送windows系统用户,默认添加到Users、Remote Desktop Users组中

* [Update] 优化小细节

* [Update] 更新翻译,删除多余代码

* [Update] 更新翻译信息
parent 9f9f2254
......@@ -16,7 +16,7 @@ from django.urls import reverse_lazy
from django.core.cache import cache
from django.db.models import Q
from common.mixins import IDInCacheFilterMixin
from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
......@@ -36,7 +36,7 @@ __all__ = [
]
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
......@@ -47,6 +47,7 @@ class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,)
success_message = _("%(hostname)s was %(action)s successfully")
def set_assets_node(self, assets):
if not isinstance(assets, list):
......@@ -169,8 +170,8 @@ class AssetGatewayApi(generics.RetrieveAPIView):
asset = get_object_or_404(Asset, pk=asset_id)
if asset.domain and \
asset.domain.gateways.filter(protocol=asset.protocol).exists():
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
asset.domain.gateways.filter(protocol='ssh').exists():
gateway = random.choice(asset.domain.gateways.filter(protocol='ssh'))
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
return Response(serializer.data)
else:
......
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
UPDATE_ASSETS_HARDWARE_TASKS = [
{
......@@ -22,6 +20,14 @@ TEST_ADMIN_USER_CONN_TASKS = [
}
}
]
TEST_WINDOWS_ADMIN_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
......@@ -34,7 +40,14 @@ TEST_SYSTEM_USER_CONN_TASKS = [
}
}
]
TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}'
TEST_ASSET_USER_CONN_TASKS = [
......@@ -45,6 +58,14 @@ TEST_ASSET_USER_CONN_TASKS = [
}
}
]
TEST_WINDOWS_ASSET_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
TASK_OPTIONS = {
......
......@@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from orgs.mixins import OrgModelForm
from ..models import Asset, AdminUser
from ..models import Asset, AdminUser, Protocol
logger = get_logger(__file__)
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
__all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm',
'ProtocolForm'
]
class ProtocolForm(forms.ModelForm):
class Meta:
model = Protocol
fields = ['name', 'port']
widgets = {
'name': forms.Select(attrs={
'class': 'form-control protocol-name'
}),
'port': forms.TextInput(attrs={
'class': 'form-control protocol-port'
}),
}
class AssetCreateForm(OrgModelForm):
PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain', 'protocol',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
......@@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
......@@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain', 'protocol',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
......@@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
......@@ -101,8 +117,8 @@ class AssetBulkUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'assets', 'port', 'admin_user', 'labels', 'platform',
'protocol', 'domain',
'assets', 'admin_user', 'labels', 'platform',
'domain',
]
widgets = {
'labels': forms.SelectMultiple(
......
......@@ -100,16 +100,18 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
private_key, public_key = super().gen_keys()
if login_mode == SystemUser.LOGIN_MANUAL or \
protocol in [SystemUser.PROTOCOL_RDP,
SystemUser.PROTOCOL_TELNET,
protocol in [SystemUser.PROTOCOL_TELNET,
SystemUser.PROTOCOL_VNC]:
system_user.auto_push = 0
auto_generate_key = False
system_user.save()
auto_generate_key = False
if auto_generate_key:
logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth()
if protocol == SystemUser.PROTOCOL_SSH:
system_user.auto_gen_auth()
elif protocol == SystemUser.PROTOCOL_RDP:
system_user.auto_gen_auth_password()
else:
system_user.set_auth(password=password, private_key=private_key,
public_key=public_key)
......
......@@ -15,4 +15,9 @@ class Migration(migrations.Migration):
name='ip',
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
),
migrations.AlterField(
model_name='asset',
name='public_ip',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Public IP'),
),
]
# Generated by Django 2.1.7 on 2019-05-22 02:58
import django.core.validators
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0027_auto_20190521_1703'),
]
operations = [
migrations.CreateModel(
name='Protocol',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Name')),
('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(1)], verbose_name='Port')),
],
),
migrations.AddField(
model_name='asset',
name='protocols',
field=models.ManyToManyField(to='assets.Protocol',
verbose_name='Protocol'),
),
]
# Generated by Django 2.1.7 on 2019-05-22 03:14
from django.db import migrations
def migrate_assets_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
assets = asset_model.objects.using(db_alias).all()
for asset in assets:
asset.protocols.create(name=asset.protocol, port=asset.port)
class Migration(migrations.Migration):
dependencies = [
('assets', '0028_protocol'),
]
operations = [
migrations.RunPython(migrate_assets_protocol),
]
......@@ -8,4 +8,3 @@ from .asset import *
from .cmd_filter import *
from .utils import *
from .authbook import *
from applications.models.remote_app import *
......@@ -12,11 +12,12 @@ from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from django.core.validators import MinValueValidator, MaxValueValidator
from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin, OrgManager
__all__ = ['Asset']
__all__ = ['Asset', 'Protocol']
logger = logging.getLogger(__name__)
......@@ -47,18 +48,13 @@ class AssetQuerySet(models.QuerySet):
return self.active()
class Asset(OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
class AssetManager(OrgManager):
def get_queryset(self):
queryset = super().get_queryset().prefetch_related("nodes", "protocols")
return queryset
class Protocol(models.Model):
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
......@@ -69,12 +65,39 @@ class Asset(OrgModelMixin):
(PROTOCOL_TELNET, 'telnet (beta)'),
(PROTOCOL_VNC, 'vnc'),
)
PORT_VALIDATORS = [MaxValueValidator(65535), MinValueValidator(1)]
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=16, choices=PROTOCOL_CHOICES,
default=PROTOCOL_SSH, verbose_name=_("Name"))
port = models.IntegerField(default=22, verbose_name=_("Port"),
validators=PORT_VALIDATORS)
def __str__(self):
return "{}:{}".format(self.name, self.port)
class Asset(OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
protocol = models.CharField(max_length=128, default=Protocol.PROTOCOL_SSH,
choices=Protocol.PROTOCOL_CHOICES,
verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocol"))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
......@@ -84,7 +107,7 @@ class Asset(OrgModelMixin):
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect
......@@ -110,7 +133,7 @@ class Asset(OrgModelMixin):
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
objects = OrgManager.from_queryset(AssetQuerySet)()
objects = AssetManager.from_queryset(AssetQuerySet)()
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
......@@ -131,19 +154,53 @@ class Asset(OrgModelMixin):
return True, ''
return False, warning
def support_ansible(self):
if self.platform in ("Windows", "Windows2016", "Other"):
return False
if self.protocol != 'ssh':
@property
def protocols_name(self):
names = []
for protocol in self.protocols.all():
names.append(protocol.name)
return names
def has_protocol(self, name):
return name in self.protocols_name
def get_protocol_by_name(self, name):
for i in self.protocols.all():
if i.name.lower() == name.lower():
return i
return None
@property
def protocol_ssh(self):
return self.get_protocol_by_name("ssh")
@property
def protocol_rdp(self):
return self.get_protocol_by_name("rdp")
@property
def ssh_port(self):
if self.protocol_ssh:
port = self.protocol_ssh.port
else:
port = 22
return port
def is_windows(self):
if self.platform in ("Windows", "Windows2016"):
return True
else:
return False
return True
def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016"):
if self.platform not in ("Windows", "Windows2016", "Other"):
return True
else:
return False
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
......@@ -172,6 +229,15 @@ class Asset(OrgModelMixin):
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
return Asset.objects.filter(filter_arg)
@property
def cpu_info(self):
info = ""
if self.cpu_model:
info += self.cpu_model
if self.cpu_count and self.cpu_cores:
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
return info
@property
def hardware_info(self):
if self.cpu_count:
......@@ -196,14 +262,16 @@ class Asset(OrgModelMixin):
cache.set(key, value, 3600*2)
def get_auth_info(self):
if self.admin_user:
self.admin_user.load_specific_asset_auth(self)
return {
'username': self.admin_user.username,
'password': self.admin_user.password,
'private_key': self.admin_user.private_key_file,
'become': self.admin_user.become_info,
}
if not self.admin_user:
return {}
self.admin_user.load_specific_asset_auth(self)
info = {
'username': self.admin_user.username,
'password': self.admin_user.password,
'private_key': self.admin_user.private_key_file,
}
return info
def as_node(self):
from .node import Node
......@@ -215,35 +283,6 @@ class Asset(OrgModelMixin):
fake_node.is_node = False
return fake_node
def to_json(self):
info = {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
}
if self.domain and self.domain.gateway_set.all():
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
return info
def _to_secret_json(self):
"""
Ansible use it create inventory
Todo: May be move to ops implements it
"""
data = self.to_json()
if self.admin_user:
self.admin_user.load_specific_asset_auth(self)
admin_user = self.admin_user
data.update({
'username': admin_user.username,
'password': admin_user.password,
'private_key': admin_user.private_key_file,
'become': admin_user.become_info,
'groups': [node.value for node in self.nodes.all()],
})
return data
def as_tree_node(self, parent_node):
from common.tree import TreeNode
icon_skin = 'file'
......@@ -265,9 +304,11 @@ class Asset(OrgModelMixin):
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'protocols': [
{"name": p.name, "port": p.port}
for p in self.protocols.all()
],
'platform': self.platform,
'protocol': self.protocol,
}
}
}
......@@ -291,10 +332,10 @@ class Asset(OrgModelMixin):
asset = cls(ip='.'.join(ip),
hostname=forgery_py.internet.user_name(True),
admin_user=choice(AdminUser.objects.all()),
port=22,
created_by='Fake')
try:
asset.save()
asset.protocols.create(name="ssh", port=22)
if nodes and len(nodes) > 3:
_nodes = random.sample(nodes, 3)
else:
......
......@@ -158,6 +158,10 @@ class AssetUser(OrgModelMixin):
private_key=private_key,
public_key=public_key)
def auto_gen_auth_password(self):
password = str(uuid.uuid4())
self.set_auth(password=password)
def _to_secret_json(self):
"""Push system user use it"""
return {
......
......@@ -201,7 +201,7 @@ class SystemUser(AssetUser):
return self.get_login_mode_display()
def is_need_push(self):
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
return True
else:
return False
......
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework.validators import ValidationError
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgResourceSerializerMixin
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset
from ..models import Asset, Protocol
from .system_user import AssetSystemUserSerializer
__all__ = [
......@@ -16,25 +17,33 @@ __all__ = [
]
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin):
class ProtocolSerializer(serializers.ModelSerializer):
class Meta:
model = Protocol
fields = ["name", "port"]
class AssetSerializer(BulkSerializerMixin, OrgResourceSerializerMixin,
serializers.ModelSerializer):
protocols = ProtocolSerializer(many=True)
"""
资产的数据结构
"""
class Meta:
model = Asset
list_serializer_class = AdaptedBulkListSerializer
# validators = [] # 解决批量导入时unique_together字段校验失败
fields = [
'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port',
'platform', 'is_active', 'public_ip', 'domain', 'admin_user',
'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
'protocols', 'platform', 'is_active', 'public_ip', 'domain',
'admin_user', 'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
'hostname_raw', 'comment', 'created_by', 'date_created',
'hardware_info', 'connectivity'
]
read_only_fields = (
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
'os', 'os_version', 'os_arch', 'hostname_raw',
'created_by', 'date_created',
......@@ -43,7 +52,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResou
'hardware_info': {'label': _('Hardware info')},
'connectivity': {'label': _('Connectivity')},
'org_name': {'label': _('Org name')}
}
@classmethod
......@@ -53,18 +61,64 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResou
.select_related('admin_user')
return queryset
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend([
'hardware_info', 'connectivity', 'org_name'
])
return fields
@staticmethod
def validate_protocols(attr):
protocols_name = [i.get("name", "ssh") for i in attr]
errors = [{} for i in protocols_name]
for i, name in enumerate(protocols_name):
if name in protocols_name[:i]:
errors[i] = {"name": _("Protocol duplicate: {}").format(name)}
if any(errors):
raise ValidationError(errors)
return attr
def create(self, validated_data):
protocols_data = validated_data.pop("protocols")
# 兼容老的api
protocol = validated_data.get("protocol")
port = validated_data.get("port")
if not protocols_data and protocol and port:
protocols_data = [{"name": protocol, "port": port}]
if not protocol and not port and protocols_data:
validated_data["protocol"] = protocols_data[0]["name"]
validated_data["port"] = protocols_data[0]["port"]
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
protocols_serializer.is_valid(raise_exception=True)
protocols = protocols_serializer.save()
instance = super().create(validated_data)
instance.protocols.set(protocols)
return instance
def update(self, instance, validated_data):
protocols_data = validated_data.pop("protocols")
# 兼容老的api
protocol = validated_data.get("protocol")
port = validated_data.get("port")
if not protocols_data and protocol and port:
protocols_data = [{"name": protocol, "port": port}]
if not protocol and not port and protocols_data:
validated_data["protocol"] = protocols_data[0]["name"]
validated_data["port"] = protocols_data[0]["port"]
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
protocols_serializer.is_valid(raise_exception=True)
protocols = protocols_serializer.save()
instance = super().update(instance, validated_data)
instance.protocols.all().delete()
instance.protocols.set(protocols)
return instance
class AssetAsNodeSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
fields = ['id', 'hostname', 'ip', 'platform', 'protocols']
class AssetGrantedSerializer(serializers.ModelSerializer):
......@@ -78,9 +132,9 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"id", "hostname", "ip", "system_users_granted",
"is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol", "org_id", "org_name",
"platform", "comment", "protocols", "org_id", "org_name",
)
@staticmethod
......
......@@ -5,6 +5,7 @@ from django.db.models.signals import post_save, m2m_changed, post_delete
from django.dispatch import receiver
from common.utils import get_logger
from common.decorator import on_transaction_commit
from .models import Asset, SystemUser, Node, AuthBook
from .tasks import (
update_assets_hardware_info_util,
......@@ -32,9 +33,12 @@ def set_asset_root_node(asset):
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
@on_transaction_commit
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
if created:
logger.info("Asset `{}` create signal received".format(instance))
# 获取资产硬件信息
update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance)
......
......@@ -3,6 +3,7 @@ import json
import re
import os
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from django.core.cache import cache
......@@ -31,7 +32,7 @@ def check_asset_can_run_ansible(asset):
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
return False
if not asset.support_ansible():
if not asset.is_support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
return False
......@@ -45,10 +46,21 @@ def clean_hosts(assets):
continue
clean_assets.append(asset)
if not clean_assets:
print(_("No assets matched, stop task"))
logger.info(_("No assets matched, stop task"))
return clean_assets
def clean_hosts_by_protocol(system_user, assets):
hosts = [
asset for asset in assets
if asset.has_protocol(system_user.protocol)
]
if not hosts:
msg = _("No assets matched related system user protocol, stop task")
logger.info(msg)
return hosts
@shared_task
def set_assets_hardware_info(assets, result, **kwargs):
"""
......@@ -96,7 +108,7 @@ def set_assets_hardware_info(assets, result, **kwargs):
___disk_total = '%s %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info)
___platform = info.get('ansible_system', 'Unknown')
# ___platform = info.get('ansible_system', 'Unknown')
___os = info.get('ansible_distribution', 'Unknown')
___os_version = info.get('ansible_distribution_version', 'Unknown')
___os_arch = info.get('ansible_architecture', 'Unknown')
......@@ -163,25 +175,53 @@ def test_asset_connectivity_util(assets, task_name=None):
if task_name is None:
task_name = _("Test assets connectivity")
hosts = clean_hosts(assets)
if not hosts:
return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS
created_by = assets[0].org_id
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_ADMIN_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
result = task.run()
summary = result[1]
created_by = assets[0].org_id
for _, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
created_by=created_by,
)
result = task.run()
summary = result[1]
results_summary['success'] &= summary['success']
results_summary['contacted'].update(summary['contacted'])
results_summary['dark'].update(summary['dark'])
for asset in assets:
if asset.hostname in summary.get('dark', {}):
if asset.hostname in results_summary.get('dark', {}):
asset.connectivity = asset.UNREACHABLE
elif asset.hostname in summary.get('contacted', []):
elif asset.hostname in results_summary.get('contacted', []):
asset.connectivity = asset.REACHABLE
else:
asset.connectivity = asset.UNKNOWN
return summary
return results_summary
@shared_task
......@@ -243,8 +283,7 @@ def test_admin_user_connectivity_manual(admin_user):
## System user connective ##
@shared_task
def set_system_user_connectivity_info(system_user, result):
summary = result[1]
def set_system_user_connectivity_info(system_user, summary):
system_user.connectivity = summary
......@@ -258,18 +297,50 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
:return:
"""
from ops.utils import update_or_create_ansible_task
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
hosts = clean_hosts(assets)
if not hosts:
return {}
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS,
run_as=system_user.username, created_by=system_user.org_id,
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_SYSTEM_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_SYSTEM_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
result = task.run()
set_system_user_connectivity_info(system_user, result)
return result
for _, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS,
run_as=system_user.username,
created_by=system_user.org_id,
)
result = task.run()
summary = result[1]
results_summary['success'] &= summary['success']
results_summary['contacted'].update(summary['contacted'])
results_summary['dark'].update(summary['dark'])
set_system_user_connectivity_info(system_user, results_summary)
return results_summary
@shared_task
......@@ -301,11 +372,7 @@ def test_system_user_connectivity_period():
#### Push system user tasks ####
def get_push_system_user_tasks(system_user):
# Set root as system user is dangerous
if system_user.username == "root":
return []
def get_push_linux_system_user_tasks(system_user):
tasks = []
if system_user.password:
tasks.append({
......@@ -320,12 +387,12 @@ def get_push_system_user_tasks(system_user):
})
tasks.extend([
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
},
'register': 'home_existed'
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
},
'register': 'home_existed'
},
{
'name': "Set home dir permission",
......@@ -364,6 +431,46 @@ def get_push_system_user_tasks(system_user):
)
}
})
return tasks
def get_push_windows_system_user_tasks(system_user):
tasks = []
if system_user.password:
tasks.append({
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'win_user',
'args': 'fullname={} '
'name={} '
'password={} '
'state=present '
'update_password=always'
'password_expired=no '
'password_never_expires=yes '
'groups="Users,Remote Desktop Users" '
'groups_action=add '
''.format(system_user.name,
system_user.username,
system_user.password),
}
})
return tasks
def get_push_system_user_tasks(host, system_user):
if host.is_unixlike():
tasks = get_push_linux_system_user_tasks(system_user)
elif host.is_windows():
tasks = get_push_windows_system_user_tasks(system_user)
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(host.hostname, host.platform)
)
logger.info(msg)
tasks = []
return tasks
......@@ -372,16 +479,29 @@ def push_system_user_util(system_user, assets, task_name):
from ops.utils import update_or_create_ansible_task
if not system_user.is_need_push():
msg = _("Push system user task skip, auto push not enable or "
"protocol is not ssh: {}").format(system_user.name)
"protocol is not ssh or rdp: {}").format(system_user.name)
logger.info(msg)
return
return {}
# Set root as system user is dangerous
if system_user.username.lower() in ["root", "administrator"]:
msg = _("For security, do not push user {}".format(system_user.username))
logger.info(msg)
return {}
hosts = clean_hosts(assets)
if not hosts:
return {}
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
for host in hosts:
system_user.load_specific_asset_auth(host)
tasks = get_push_system_user_tasks(system_user)
tasks = get_push_system_user_tasks(host, system_user)
if not tasks:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
......@@ -423,6 +543,23 @@ def test_admin_user_connectability_period():
pass
#### Test Asset user connectivity task ####
def get_test_asset_user_connectivity_tasks(asset):
if asset.is_unixlike():
tasks = const.TEST_ASSET_USER_CONN_TASKS
elif asset.is_windows():
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(asset.hostname, asset.platform)
)
logger.info(msg)
tasks = []
return tasks
@shared_task
def set_asset_user_connectivity_info(asset_user, result):
summary = result[1]
......@@ -437,10 +574,14 @@ def test_asset_user_connectivity_util(asset_user, task_name):
:return:
"""
from ops.utils import update_or_create_ansible_task
tasks = const.TEST_ASSET_USER_CONN_TASKS
if not check_asset_can_run_ansible(asset_user.asset):
return
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
if not tasks:
return
task, created = update_or_create_ansible_task(
task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS,
......
......@@ -95,41 +95,97 @@ var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
var shell_id = '#' + '{{ form.shell.id_for_label }}';
var need_change_field = [
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
];
var need_change_field_login_mode = [
auto_generate_key, private_key_id, auto_push_id, password_id
];
function protocolChange() {
function autoLoginModeProtocol() {
// 协议+自动登录模式字段控制
$('#auth_title_id').removeClass('hidden');
var protocol = $(protocol_id + " option:selected").text();
if (protocol === 'rdp' || protocol === 'vnc') {
if (protocol === 'rdp') {
authFieldsDisplay();
$(auto_generate_key).closest('.form-group').removeClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').removeClass('hidden');
$('#command-filter-block').addClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'vnc') {
$('.auth-fields').removeClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').addClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden')
});
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'telnet (beta)') {
$('.auth-fields').removeClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').removeClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden')
});
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else {
if($(login_mode_id).val() === 'manual'){
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
return
}
authFieldsDisplay();
$(auto_generate_key).closest('.form-group').removeClass('hidden');
$(private_key_id).closest('.form-group').removeClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').removeClass('hidden');
$('#command-filter-block').removeClass('hidden');
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
}
}
function manualLoginModeProtocol() {
// 协议+手动登录模式字段控制
$('#auth_title_id').addClass('hidden');
var protocol = $(protocol_id + " option:selected").text();
if (protocol === 'rdp') {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').addClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'vnc') {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').addClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'telnet (beta)') {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').removeClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').removeClass('hidden')
});
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').removeClass('hidden');
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
}
}
function authFieldsDisplay() {
......@@ -139,34 +195,29 @@ function authFieldsDisplay() {
$('.auth-fields').removeClass('hidden');
}
}
function loginModeChange(){
if ($(login_mode_id).val() === 'manual'){
$('#auth_title_id').addClass('hidden');
$.each(need_change_field_login_mode, function(index, value){
$(value).closest('.form-group').addClass('hidden')
})
function fieldDisplay(){
var login_mode = $(login_mode_id).val();
if (login_mode === 'manual'){
manualLoginModeProtocol();
}
else if($(login_mode_id).val() === 'auto'){
$('#auth_title_id').removeClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden')
protocolChange();
else if(login_mode === 'auto'){
autoLoginModeProtocol();
}
}
$(document).ready(function () {
$('.select2').select2();
authFieldsDisplay();
protocolChange();
loginModeChange();
})
.on('change', protocol_id, function(){
protocolChange();
fieldDisplay();
})
.on('change', auto_generate_key, function(){
authFieldsDisplay();
})
.on('change', login_mode_id, function(){
loginModeChange();
fieldDisplay();
})
.on('change', protocol_id, function(){
fieldDisplay();
})
</script>
......
......@@ -62,7 +62,7 @@
<div class="panel-body">
<table class="table">
<tbody>
{% if asset.protocol == 'ssh' %}
{% if asset.is_support_ansible %}
<tr class="no-borders-tr">
<td>{% trans 'Test connective' %}:</td>
<td>
......@@ -118,7 +118,7 @@ function initAssetUserTable() {
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-username="DEFAULT_USERNAME">{% trans "View auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
btn += view_btn;
{% if asset.protocol == 'ssh' %}
{% if asset.is_support_ansible %}
btn += test_btn;
{% endif %}
$(td).html(btn);
......
......@@ -16,12 +16,24 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Protocols' %}</h3>
<div class="protocols">
{% for fm in formset.forms %}
<div class="form-group">
<div class="col-md-2 col-md-offset-2" style="text-align: right">{{ fm.name }}</div>
<div class="col-md-6">{{ fm.port }}</div>
<div class="col-md-1" style="padding: 6px 0">
<a class="btn btn-danger btn-xs btn-protocol btn-del"><span class="fa fa-minus"></span> </a>
<a class="btn btn-primary btn-xs btn-protocol btn-add" style="display: none"><span class="fa fa-plus"></span></a>
</div>
</div>
{% endfor %}
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
......@@ -55,6 +67,8 @@
{% endif %}
</div>
</div>
{% block extra %}
{% endblock %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
......@@ -73,11 +87,25 @@
{% block custom_foot_js %}
<script>
var instanceId = "{{ object.id }}";
var protocolLen = 0;
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
function protocolBtnShow() {
$(".btn-protocol.btn-add").hide();
$(".btn-protocol.btn-add:last").show();
var btnDel = $(".btn-protocol.btn-del");
if (btnDel.length === 1) {
btnDel.addClass("disabled")
} else {
btnDel.removeClass("disabled")
}
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
......@@ -89,20 +117,133 @@ $(document).ready(function () {
$('#id_nodes.select2').select2({
closeOnSelect: false
});
$("#id_protocol").change(function (){
var protocol = $("#id_protocol option:selected").text();
var port = 22;
if(protocol === 'rdp'){
port = 3389;
}
else if(protocol === 'telnet (beta)'){
port = 23;
protocolBtnShow()
})
.on("change", "#id_platform", function () {
if (instanceId !== "") {
return
}
var platform = $(this).val();
var protocolRef = $(".protocols").find("select").first()
var protocol = protocolRef.val();
var protocolShould = "";
if (platform.startsWith("Windows")){
protocolShould = "rdp"
} else {
protocolShould = "ssh"
}
if (protocol !== protocolShould) {
protocolRef.val(protocolShould);
protocolRef.trigger("change")
}
})
.on("click", ".btn-protocol.btn-del", function () {
$(this).parent().parent().remove();
protocolBtnShow()
})
.on("click", ".btn-protocol.btn-add", function () {
var protocol = "";
var protocolsRef = $(".protocols");
var firstProtocolForm = protocolsRef.children().first();
var newProtocolForm = firstProtocolForm.clone();
var protocolChoices = $.map($(firstProtocolForm.find('select option')), function (option) {
return option.value
});
var protocolsSet = $.map(protocolsRef.find('select option:selected'), function(option) {
return option.value
});
for (var i=0;i<protocolChoices.length;i++) {
var p = protocolChoices[i];
if (protocolsSet.indexOf(p) === -1) {
protocol = p;
break
}
else if(protocol === 'vnc'){
}
if (protocol === "") {
return
}
if (protocolLen === 0) {
protocolLen = protocolsRef.length;
}
var selectName = "form-" + protocolLen + "-name";
var selectId = "id_" + selectName;
var portName = "form-" + protocolLen + "-port";
var portId = "id_" + portName;
newProtocolForm.find("select").prop("name", selectName).prop("id", selectId);
newProtocolForm.find("input").prop("name", portName).prop("id", portId);
newProtocolForm.find("option[value='" + protocol + "']").attr("selected", true);
protocolsRef.append(newProtocolForm);
protocolLen += 1;
$("#" + selectId).trigger("change");
protocolBtnShow()
})
.on("change", ".protocol-name", function () {
var name = $(this).val();
var port = 22;
switch (name) {
case "ssh":
port = 22;
break;
case "rdp":
port = 3389;
break;
case "telnet":
port = 21;
break;
case "vnc":
port = 5901;
}
$("#id_port").val(port);
});
break;
default:
port = 22;
break
}
$(this).parent().parent().find(".protocol-port").val(port);
})
</script>
{% block form_submit %}
<script>
$(document).ready(function () {
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-assets:asset-list' %}';
var redirect_to = '{% url "assets:asset-list" %}';
var form = $("form");
var protocols = {};
var data = form.serializeObject();
$.each(data, function (k, v) {
if (k.startsWith("form")){
delete data[k];
var _k = k.split("-");
var formName = _k.slice(0, 2).join("-");
var key = _k[_k.length-1];
if (!protocols[formName]) {
protocols[formName] = {}
}
protocols[formName][key] = v
}
});
protocols = $.map(protocols, function ( v) {
return v
});
data["protocols"] = protocols;
if (typeof data.labels === "string") {
data["labels"] = [data["labels"]];
}
if (typeof data["nodes"] == "string") {
data["nodes"] = [data["nodes"]]
}
var props = {
url: the_url,
data: data,
method: "POST",
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}
{% endblock %}
\ No newline at end of file
......@@ -69,12 +69,13 @@
<td><b>{{ asset.public_ip|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Port' %}:</td>
<td><b>{{ asset.port }}</b></td>
</tr>
<tr>
<td>{% trans 'Protocol' %}:</td>
<td><b>{{ asset.protocol }}</b></td>
<td>{% trans 'Protocol' %}</td>
<td>
{% for protocol in asset.protocols.all %}
<b>{{ protocol.name }}:</b>
{{ protocol.port }}
{% endfor %}
</td>
</tr>
<tr>
<td>{% trans 'Admin user' %}:</td>
......@@ -94,7 +95,7 @@
</tr>
<tr>
<td>{% trans 'CPU' %}:</td>
<td><b>{{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }}</b></td>
<td><b>{{ asset.cpu_info }}</b></td>
</tr>
<tr>
<td>{% trans 'Memory' %}:</td>
......@@ -166,7 +167,7 @@
</span>
</td>
</tr>
{% if asset.protocol == 'ssh' %}
{% if asset.is_support_ansible %}
<tr>
<td>{% trans 'Refresh hardware' %}:</td>
<td>
......
{% extends '_base_create_update.html' %}
{% load static %}
{% extends 'assets/asset_create.html' %}
{% load bootstrap3 %}
{% load i18n %}
{% load asset_tags %}
{% load common_tags %}
{% block custom_head_css_js_create %}
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/inputTags.jquery.min.js" %}"></script>
{% endblock %}
{% block form %}
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Labels' %}</h3>
<div class="form-group">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}">
{% for label in labels %}
{% if label in form.labels.initial %}
<option value="{{ label.id }}" selected>{{ label.value }}</option>
{% else %}
<option value="{{ label.id }}">{{ label.value }}</option>
{% endif %}
{% endfor %}
</optgroup>
{% endfor %}
</select>
</div>
</div>
{% block extra %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Configuration' %}</h3>
{% bootstrap_field form.number layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.comment layout="horizontal" %}
{% bootstrap_field form.is_active layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script>
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
{% block form_submit %}
<script>
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}';
var redirect_to = '{% url "assets:asset-list" %}';
var form = $("form");
var protocols = {};
var data = form.serializeObject();
$.each(data, function (k, v) {
if (k.startsWith("form")){
delete data[k];
var _k = k.split("-");
var formName = _k.slice(0, 2).join("-");
var key = _k[_k.length-1];
if (!protocols[formName]) {
protocols[formName] = {}
}
protocols[formName][key] = v
}
});
protocols = $.map(protocols, function ( v) {
return v
});
data["protocols"] = protocols;
if (typeof data["nodes"] == "string") {
data["nodes"] = [data["nodes"]]
}
var props = {
url: the_url,
data: data,
method: "PUT",
form: form,
redirect_to: redirect_to
};
formSubmit(props);
});
</script>
{% endblock %}
\ No newline at end of file
......@@ -118,7 +118,7 @@
<div class="panel-body">
<table class="table">
<tbody>
<tr class="only-ssh">
<tr class="only-ssh-rdp">
<td width="50%">{% trans 'Auto push' %}:</td>
<td>
<span class="pull-right">
......@@ -135,7 +135,7 @@
</td>
</tr>
{% if system_user.auto_push %}
<tr class="only-ssh">
<tr class="only-ssh-rdp">
<td width="50%">{% trans 'Push system user now' %}:</td>
<td>
<span style="float: right">
......@@ -144,7 +144,7 @@
</td>
</tr>
{% endif %}
<tr class="only-ssh">
<tr>
<td width="50%">{% trans 'Test assets connective' %}:</td>
<td>
<span style="float: right">
......@@ -219,8 +219,12 @@ function updateCommandFilters(command_filters) {
});
}
$(document).ready(function () {
if($('#id_protocol_type').text() === 'rdp'){
$('.only-ssh').addClass('hidden')
var protocol = $('#id_protocol_type').text();
if(protocol !== 'ssh'){
$('.only-ssh').addClass("hidden")
}
if(["ssh", "rdp"].indexOf(protocol) === -1){
$('.only-ssh-rdp').addClass('hidden');
}
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
$('.select2').select2()
......
......@@ -9,7 +9,7 @@
{# 目前还不支持Windows的自动推送#}
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch, Windows) does not support ansible, please manually fill in the account password. Automatic push for Windows is not currently supported.' %}
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %}
</div>
{% endblock %}
......
......@@ -23,6 +23,7 @@ from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.contrib.messages.views import SuccessMessageMixin
from django.forms.formsets import formset_factory
from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger
......@@ -30,8 +31,6 @@ from common.permissions import AdminUserRequiredMixin
from common.const import (
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
)
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from orgs.utils import current_org
from .. import forms
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
......@@ -102,10 +101,30 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
form["nodes"].initial = node
return form
def get_protocol_formset(self):
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
if self.request.method == "POST":
formset = ProtocolFormset(self.request.POST)
else:
formset = ProtocolFormset()
return formset
def form_valid(self, form):
formset = self.get_protocol_formset()
valid = formset.is_valid()
if not valid:
return self.form_invalid(form)
protocols = formset.save()
instance = super().form_valid(form)
instance.protocols.set(protocols)
return instance
def get_context_data(self, **kwargs):
formset = self.get_protocol_formset()
context = {
'app': _('Assets'),
'action': _('Create asset'),
'formset': formset,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -160,10 +179,21 @@ class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
template_name = 'assets/asset_update.html'
success_url = reverse_lazy('assets:asset-list')
def get_protocol_formset(self):
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
if self.request.method == "POST":
formset = ProtocolFormset(self.request.POST)
else:
initial_data = [{"name": p.name, "port": p.port} for p in self.object.protocols.all()]
formset = ProtocolFormset(initial=initial_data)
return formset
def get_context_data(self, **kwargs):
formset = self.get_protocol_formset()
context = {
'app': _('Assets'),
'action': _('Update asset'),
'formset': formset,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......
# -*- coding: utf-8 -*-
#
from django.db import transaction
def on_transaction_commit(func):
"""
如果不调用on_commit, 对象创建时添加多对多字段值失败
"""
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
......@@ -5,6 +5,7 @@ from django.http import JsonResponse
from django.utils import timezone
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from rest_framework.utils import html
from rest_framework.settings import api_settings
from rest_framework.exceptions import ValidationError
......@@ -203,3 +204,26 @@ class DatetimeSearchMixin:
def get(self, request, *args, **kwargs):
self.get_date_range()
return super().get(request, *args, **kwargs)
class ApiMessageMixin:
success_message = _("%(name)s was %(action)s successfully")
_action_map = {"create": _("create"), "update": _("update")}
def get_success_message(self, cleaned_data):
data = {k: v for k, v in cleaned_data.items()}
action = getattr(self, "action", "create")
data["action"] = self._action_map.get(action)
message = self.success_message % data
return message
def dispatch(self, request, *args, **kwargs):
resp = super().dispatch(request, *args, **kwargs)
if request.method.lower() in ("get", "delete"):
return resp
if resp.status_code >= 400:
return resp
message = self.get_success_message(resp.data)
if message:
messages.success(request, message)
return resp
......@@ -3,5 +3,22 @@
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _
from rest_framework.validators import (
UniqueTogetherValidator, ValidationError
)
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
\ No newline at end of file
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
class ProjectUniqueValidator(UniqueTogetherValidator):
def __call__(self, attrs):
try:
super().__call__(attrs)
except ValidationError as e:
errors = {}
for field in self.fields:
if field == "org_id":
continue
errors[field] = _('This field must be unique.')
raise ValidationError(errors)
......@@ -287,7 +287,10 @@ class Config(dict):
return v
try:
v = tp(v)
if tp in [list, dict]:
v = json.loads(v)
else:
v = tp(v)
except Exception:
pass
return v
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-12 17:56+0800\n"
"POT-Creation-Date: 2019-06-13 18:56+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -76,7 +76,7 @@ msgstr "运行参数"
#: applications/templates/applications/remote_app_list.html:22
#: applications/templates/applications/user_remote_app_list.html:18
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:279 assets/models/authbook.py:27
#: assets/models/asset.py:320 assets/models/authbook.py:27
#: assets/serializers/admin_user.py:23 assets/serializers/system_user.py:28
#: assets/templates/assets/admin_user_list.html:49
#: assets/templates/assets/domain_detail.html:60
......@@ -95,7 +95,7 @@ msgstr "运行参数"
#: terminal/templates/terminal/session_list.html:41
#: terminal/templates/terminal/session_list.html:72
#: xpack/plugins/change_auth_plan/forms.py:114
#: xpack/plugins/change_auth_plan/models.py:409
#: xpack/plugins/change_auth_plan/models.py:413
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
......@@ -132,11 +132,11 @@ msgstr "系统用户"
#: applications/templates/applications/remote_app_detail.html:53
#: applications/templates/applications/remote_app_list.html:20
#: applications/templates/applications/user_remote_app_list.html:16
#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146
#: assets/models/base.py:26 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/templates/assets/admin_user_detail.html:56
#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:148
#: assets/models/asset.py:72 assets/models/base.py:26
#: assets/models/cluster.py:18 assets/models/cmd_filter.py:20
#: assets/models/domain.py:20 assets/models/group.py:20
#: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:47
#: assets/templates/assets/cmd_filter_detail.html:61
#: assets/templates/assets/cmd_filter_list.html:24
......@@ -173,7 +173,7 @@ msgstr "系统用户"
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:53
#: xpack/plugins/change_auth_plan/forms.py:97
#: xpack/plugins/change_auth_plan/models.py:58
#: xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119
......@@ -204,11 +204,11 @@ msgstr "参数"
#: applications/models/remote_app.py:43
#: applications/templates/applications/remote_app_detail.html:77
#: assets/models/asset.py:109 assets/models/base.py:34
#: assets/models/asset.py:132 assets/models/base.py:34
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:128
#: assets/templates/assets/asset_detail.html:129
#: assets/templates/assets/cmd_filter_detail.html:77
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100
......@@ -218,7 +218,7 @@ msgstr "参数"
#: perms/templates/perms/remote_app_permission_detail.html:90
#: users/models/user.py:102 users/serializers/v1.py:72
#: users/templates/users/user_detail.html:111
#: xpack/plugins/change_auth_plan/models.py:103
#: xpack/plugins/change_auth_plan/models.py:106
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127
msgid "Created by"
......@@ -228,7 +228,7 @@ msgstr "创建者"
# msgstr "创建者"
#: applications/models/remote_app.py:46
#: applications/templates/applications/remote_app_detail.html:73
#: assets/models/asset.py:110 assets/models/cluster.py:26
#: assets/models/asset.py:133 assets/models/cluster.py:26
#: assets/models/domain.py:23 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/admin_user.py:37
#: assets/templates/assets/admin_user_detail.html:64
......@@ -256,13 +256,13 @@ msgstr "创建日期"
#: applications/templates/applications/remote_app_detail.html:81
#: applications/templates/applications/remote_app_list.html:24
#: applications/templates/applications/user_remote_app_list.html:20
#: assets/models/asset.py:111 assets/models/base.py:31
#: assets/models/asset.py:134 assets/models/base.py:31
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
#: assets/models/domain.py:53 assets/models/group.py:23
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:53
#: assets/templates/assets/asset_detail.html:136
#: assets/templates/assets/asset_detail.html:137
#: assets/templates/assets/cmd_filter_detail.html:65
#: assets/templates/assets/cmd_filter_list.html:27
#: assets/templates/assets/cmd_filter_rule_list.html:62
......@@ -282,7 +282,7 @@ msgstr "创建日期"
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:37
#: users/templates/users/user_profile.html:134
#: xpack/plugins/change_auth_plan/models.py:99
#: xpack/plugins/change_auth_plan/models.py:102
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125
......@@ -310,8 +310,7 @@ msgstr "远程应用"
#: assets/templates/assets/_system_user.html:75
#: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/asset_create.html:81
#: assets/templates/assets/cmd_filter_create_update.html:15
#: assets/templates/assets/cmd_filter_rule_create_update.html:40
#: assets/templates/assets/domain_create_update.html:16
......@@ -347,9 +346,8 @@ msgstr "重置"
#: assets/templates/assets/_system_user.html:76
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_create.html:82
#: assets/templates/assets/asset_list.html:125
#: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/cmd_filter_create_update.html:16
#: assets/templates/assets/cmd_filter_rule_create_update.html:41
#: assets/templates/assets/domain_create_update.html:17
......@@ -562,9 +560,9 @@ msgstr "连接"
#: assets/templates/assets/system_user_detail.html:22
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
#: assets/views/admin_user.py:63 assets/views/admin_user.py:78
#: assets/views/admin_user.py:102 assets/views/asset.py:53
#: assets/views/asset.py:69 assets/views/asset.py:107 assets/views/asset.py:148
#: assets/views/asset.py:165 assets/views/asset.py:189
#: assets/views/admin_user.py:102 assets/views/asset.py:52
#: assets/views/asset.py:68 assets/views/asset.py:125 assets/views/asset.py:167
#: assets/views/asset.py:194 assets/views/asset.py:219
#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46
#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78
#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130
......@@ -575,7 +573,7 @@ msgstr "连接"
#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69
#: assets/views/system_user.py:28 assets/views/system_user.py:44
#: assets/views/system_user.py:60 assets/views/system_user.py:74
#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65
#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:68
msgid "Assets"
msgstr "资产管理"
......@@ -595,7 +593,12 @@ msgstr "远程应用详情"
msgid "My RemoteApp"
msgstr "我的远程应用"
#: assets/api/asset.py:128
#: assets/api/asset.py:50
#, python-format
msgid "%(hostname)s was %(action)s successfully"
msgstr "%(hostname)s %(action)s成功"
#: assets/api/asset.py:129
msgid "Please select assets that need to be updated"
msgstr "请选择需要更新的资产"
......@@ -611,47 +614,44 @@ msgstr "更新节点资产硬件信息: {}"
msgid "Test if the assets under the node are connectable: {}"
msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133
#: assets/templates/assets/asset_detail.html:194
#: assets/templates/assets/asset_detail.html:202
#: assets/forms/asset.py:45 assets/models/asset.py:103
#: assets/models/user.py:133 assets/templates/assets/asset_detail.html:195
#: assets/templates/assets/asset_detail.html:203
#: assets/templates/assets/system_user_asset.html:95
#: perms/models/asset_permission.py:38
#: xpack/plugins/change_auth_plan/models.py:69
#: xpack/plugins/change_auth_plan/models.py:72
msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:30 assets/forms/asset.py:66 assets/models/asset.py:84
#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:107
#: assets/models/cluster.py:19 assets/models/user.py:91
#: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24
#: assets/templates/assets/asset_detail.html:81 templates/_nav.html:24
#: xpack/plugins/cloud/models.py:124
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65
#: xpack/plugins/orgs/templates/orgs/org_list.html:18
msgid "Admin user"
msgstr "管理用户"
#: assets/forms/asset.py:33 assets/forms/asset.py:69 assets/forms/asset.py:109
#: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38
#: assets/forms/asset.py:51 assets/forms/asset.py:86 assets/forms/asset.py:125
#: assets/templates/assets/asset_create.html:48
#: assets/templates/assets/asset_create.html:50
#: assets/templates/assets/asset_list.html:93
#: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:33
#: xpack/plugins/orgs/templates/orgs/org_list.html:20
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:37 assets/forms/asset.py:73 assets/models/asset.py:79
#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:102
#: assets/models/domain.py:26 assets/models/domain.py:52
#: assets/templates/assets/asset_detail.html:84
#: assets/templates/assets/asset_detail.html:85
#: assets/templates/assets/user_asset_list.html:173
#: xpack/plugins/orgs/templates/orgs/org_list.html:17
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:41 assets/forms/asset.py:63 assets/forms/asset.py:77
#: assets/forms/asset.py:112 assets/models/node.py:31
#: assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35
#: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93
#: assets/forms/asset.py:128 assets/models/node.py:31
#: assets/templates/assets/asset_create.html:42
#: perms/forms/asset_permission.py:49 perms/forms/asset_permission.py:59
#: perms/models/asset_permission.py:57
#: perms/templates/perms/asset_permission_list.html:57
......@@ -666,7 +666,7 @@ msgstr "网域"
msgid "Node"
msgstr "节点"
#: assets/forms/asset.py:45 assets/forms/asset.py:81
#: assets/forms/asset.py:62 assets/forms/asset.py:97
msgid ""
"root or other NOPASSWD sudo privilege user existed in asset,If asset is "
"windows or other set any one, more see admin user left menu"
......@@ -674,17 +674,17 @@ msgstr ""
"root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一"
"个, 更多信息查看左侧 `管理用户` 菜单"
#: assets/forms/asset.py:48 assets/forms/asset.py:84
#: assets/forms/asset.py:65 assets/forms/asset.py:100
msgid "Windows 2016 RDP protocol is different, If is window 2016, set it"
msgstr "Windows 2016的RDP协议与之前不同,如果是请设置"
#: assets/forms/asset.py:49 assets/forms/asset.py:85
#: assets/forms/asset.py:66 assets/forms/asset.py:101
msgid ""
"If your have some network not connect with each other, you can set domain"
msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录"
#: assets/forms/asset.py:92 assets/forms/asset.py:96 assets/forms/domain.py:17
#: assets/forms/label.py:15
#: assets/forms/asset.py:108 assets/forms/asset.py:112
#: assets/forms/domain.py:17 assets/forms/label.py:15
#: perms/templates/perms/asset_permission_asset.html:88
#: xpack/plugins/change_auth_plan/forms.py:105
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:84
......@@ -699,7 +699,7 @@ msgstr "不能包含特殊字符"
msgid "SSH gateway support proxy SSH,RDP,VNC"
msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147
#: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:149
#: assets/models/base.py:27
#: assets/templates/assets/_asset_user_auth_modal.html:15
#: assets/templates/assets/_asset_user_view_auth_modal.html:31
......@@ -721,8 +721,8 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: users/templates/users/user_list.html:36
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_auth_plan/forms.py:99
#: xpack/plugins/change_auth_plan/models.py:60
#: xpack/plugins/change_auth_plan/models.py:405
#: xpack/plugins/change_auth_plan/models.py:63
#: xpack/plugins/change_auth_plan/models.py:409
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
......@@ -749,8 +749,8 @@ msgstr "密码或密钥密码"
#: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40
#: users/templates/users/user_update.html:20
#: xpack/plugins/change_auth_plan/models.py:90
#: xpack/plugins/change_auth_plan/models.py:260
#: xpack/plugins/change_auth_plan/models.py:93
#: xpack/plugins/change_auth_plan/models.py:264
msgid "Password"
msgstr "密码"
......@@ -767,21 +767,21 @@ msgstr "不合法的密钥,仅支持RSA/DSA格式的密钥"
msgid "Password and private key file must be input one"
msgstr "密码和私钥, 必须输入一个"
#: assets/forms/user.py:134
#: assets/forms/user.py:136
msgid "* Automatic login mode must fill in the username."
msgstr "自动登录模式,必须填写用户名"
#: assets/forms/user.py:149 assets/models/cmd_filter.py:31
#: assets/forms/user.py:151 assets/models/cmd_filter.py:31
#: assets/models/user.py:141 assets/templates/assets/_system_user.html:66
#: assets/templates/assets/system_user_detail.html:165
msgid "Command filter"
msgstr "命令过滤器"
#: assets/forms/user.py:153
#: assets/forms/user.py:155
msgid "Auto push system user to asset"
msgstr "自动推送系统用户到资产"
#: assets/forms/user.py:154
#: assets/forms/user.py:156
msgid ""
"1-100, High level will be using login asset as default, if user was granted "
"more than 2 system user"
......@@ -789,17 +789,26 @@ msgstr ""
"1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为"
"默认登录用户"
#: assets/forms/user.py:156
#: assets/forms/user.py:158
msgid ""
"If you choose manual login mode, you do not need to fill in the username and "
"password."
msgstr "如果选择手动登录模式,用户名和密码可以不填写"
#: assets/forms/user.py:158
#: assets/forms/user.py:160
msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"
msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/models/asset.py:74 assets/models/domain.py:49
#: assets/models/asset.py:73 assets/models/asset.py:98
#: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:50
#: assets/templates/assets/domain_gateway_list.html:69
#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/user_asset_list.html:168
#: settings/templates/settings/replay_storage_create.html:59
msgid "Port"
msgstr "端口"
#: assets/models/asset.py:93 assets/models/domain.py:49
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:49
#: assets/templates/assets/asset_detail.html:64
......@@ -816,7 +825,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:75 assets/templates/assets/_asset_list_modal.html:45
#: assets/models/asset.py:94 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/_asset_user_auth_modal.html:9
#: assets/templates/assets/_asset_user_view_auth_modal.html:25
#: assets/templates/assets/admin_user_assets.html:48
......@@ -833,8 +842,9 @@ msgstr "IP"
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:76 assets/models/domain.py:51
#: assets/models/user.py:136 assets/templates/assets/asset_detail.html:76
#: assets/models/asset.py:97 assets/models/asset.py:100
#: assets/models/domain.py:51 assets/models/user.py:136
#: assets/templates/assets/asset_detail.html:72
#: assets/templates/assets/domain_gateway_list.html:70
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:53
......@@ -843,108 +853,97 @@ msgstr "主机名"
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:77 assets/models/domain.py:50
#: assets/templates/assets/admin_user_assets.html:50
#: assets/templates/assets/asset_detail.html:72
#: assets/templates/assets/domain_gateway_list.html:69
#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/user_asset_list.html:168
#: settings/templates/settings/replay_storage_create.html:59
msgid "Port"
msgstr "端口"
#: assets/models/asset.py:78 assets/templates/assets/asset_detail.html:108
#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:109
#: assets/templates/assets/user_asset_list.html:170
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:81 assets/models/cmd_filter.py:21
#: assets/models/asset.py:104 assets/models/cmd_filter.py:21
#: assets/models/domain.py:54 assets/models/label.py:22
#: assets/templates/assets/asset_detail.html:116
#: assets/templates/assets/asset_detail.html:117
#: assets/templates/assets/user_asset_list.html:174
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:68
#: assets/models/asset.py:110 assets/templates/assets/asset_detail.html:68
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:124
#: assets/models/asset.py:111 assets/templates/assets/asset_detail.html:125
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:88
#: assets/models/asset.py:114 assets/templates/assets/asset_detail.html:89
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:92
#: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:93
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:120
#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:121
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:95
#: assets/models/asset.py:118
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:96
#: assets/models/asset.py:119
#: xpack/plugins/license/templates/license/license_detail.html:80
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:97
#: assets/models/asset.py:120
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:98
#: assets/models/asset.py:121
msgid "CPU vcpus"
msgstr "CPU总数"
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:100
#: assets/models/asset.py:122 assets/templates/assets/asset_detail.html:101
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:100
#: assets/models/asset.py:123
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:101
#: assets/models/asset.py:124
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:103 assets/templates/assets/asset_detail.html:112
#: assets/models/asset.py:126 assets/templates/assets/asset_detail.html:113
#: assets/templates/assets/user_asset_list.html:171
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:104
#: assets/models/asset.py:127
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:105
#: assets/models/asset.py:128
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:106
#: assets/models/asset.py:129
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:108 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:231
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:26
#: assets/models/asset.py:131 assets/templates/assets/asset_create.html:46
#: assets/templates/assets/asset_detail.html:232 templates/_nav.html:26
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:117 assets/models/base.py:38
#: assets/models/asset.py:140 assets/models/base.py:38
#: assets/serializers/admin_user.py:22 assets/serializers/system_user.py:19
#: assets/templates/assets/admin_user_list.html:51
#: assets/templates/assets/system_user_list.html:57
msgid "Unreachable"
msgstr "不可达"
#: assets/models/asset.py:118 assets/models/base.py:39
#: assets/models/asset.py:141 assets/models/base.py:39
#: assets/serializers/admin_user.py:24 assets/serializers/system_user.py:27
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/admin_user_list.html:50
......@@ -956,7 +955,7 @@ msgstr "不可达"
msgid "Reachable"
msgstr "可连接"
#: assets/models/asset.py:119 assets/models/base.py:40
#: assets/models/asset.py:142 assets/models/base.py:40
#: authentication/utils.py:9 xpack/plugins/license/models.py:78
msgid "Unknown"
msgstr "未知"
......@@ -975,13 +974,13 @@ msgstr "版本"
msgid "AuthBook"
msgstr ""
#: assets/models/base.py:29 xpack/plugins/change_auth_plan/models.py:94
#: xpack/plugins/change_auth_plan/models.py:267
#: assets/models/base.py:29 xpack/plugins/change_auth_plan/models.py:97
#: xpack/plugins/change_auth_plan/models.py:271
msgid "SSH private key"
msgstr "ssh密钥"
#: assets/models/base.py:30 xpack/plugins/change_auth_plan/models.py:97
#: xpack/plugins/change_auth_plan/models.py:263
#: assets/models/base.py:30 xpack/plugins/change_auth_plan/models.py:100
#: xpack/plugins/change_auth_plan/models.py:267
msgid "SSH public key"
msgstr "ssh公钥"
......@@ -1204,18 +1203,22 @@ msgstr "%(value)s is not an even number"
msgid "Date updated"
msgstr "更新日期"
#: assets/serializers/asset.py:43
#: assets/serializers/asset.py:52
msgid "Hardware info"
msgstr "硬件信息"
#: assets/serializers/asset.py:44
#: assets/serializers/asset.py:53
msgid "Connectivity"
msgstr "连接"
#: assets/serializers/asset.py:45
#: assets/serializers/asset.py:54
msgid "Org name"
msgstr "组织名"
#: assets/serializers/asset.py:70
msgid "Protocol duplicate: {}"
msgstr "协议重复: {}"
#: assets/serializers/asset_user.py:23 users/forms.py:247
#: users/models/user.py:91 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:46
......@@ -1237,72 +1240,86 @@ msgstr "可连接资产"
msgid "Login mode display"
msgstr "登录模式显示"
#: assets/tasks.py:31
#: assets/tasks.py:32
msgid "Asset has been disabled, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks.py:35
#: assets/tasks.py:36
msgid "Asset may not be support ansible, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks.py:48
#: assets/tasks.py:49
msgid "No assets matched, stop task"
msgstr "没有匹配到资产,结束任务"
#: assets/tasks.py:73
#: assets/tasks.py:59
msgid "No assets matched related system user protocol, stop task"
msgstr "没有匹配到与系统用户协议相关的资产,结束任务"
#: assets/tasks.py:85
msgid "Get asset info failed: {}"
msgstr "获取资产信息失败:{}"
#: assets/tasks.py:123
#: assets/tasks.py:135
msgid "Update some assets hardware info"
msgstr "更新资产硬件信息"
#: assets/tasks.py:140
#: assets/tasks.py:152
msgid "Update asset hardware info: {}"
msgstr "更新资产硬件信息: {}"
#: assets/tasks.py:165
#: assets/tasks.py:177
msgid "Test assets connectivity"
msgstr "测试资产可连接性"
#: assets/tasks.py:189
#: assets/tasks.py:229
msgid "Test assets connectivity: {}"
msgstr "测试资产可连接性: {}"
#: assets/tasks.py:231
#: assets/tasks.py:271
msgid "Test admin user connectivity period: {}"
msgstr "定期测试管理账号可连接性: {}"
#: assets/tasks.py:238
#: assets/tasks.py:278
msgid "Test admin user connectivity: {}"
msgstr "测试管理行号可连接性: {}"
#: assets/tasks.py:277
#: assets/tasks.py:348
msgid "Test system user connectivity: {}"
msgstr "测试系统用户可连接性: {}"
#: assets/tasks.py:284
#: assets/tasks.py:355
msgid "Test system user connectivity: {} => {}"
msgstr "测试系统用户可连接性: {} => {}"
#: assets/tasks.py:297
#: assets/tasks.py:368
msgid "Test system user connectivity period: {}"
msgstr "定期测试系统用户可连接性: {}"
#: assets/tasks.py:374
#: assets/tasks.py:469 assets/tasks.py:555
#: xpack/plugins/change_auth_plan/models.py:522
msgid "The asset {} system platform {} does not support run Ansible tasks"
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
#: assets/tasks.py:481
msgid ""
"Push system user task skip, auto push not enable or protocol is not ssh: {}"
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}"
"Push system user task skip, auto push not enable or protocol is not ssh or "
"rdp: {}"
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh或rdp: {}"
#: assets/tasks.py:488
msgid "For security, do not push user {}"
msgstr "为了安全,禁止推送用户 {}"
#: assets/tasks.py:396 assets/tasks.py:410
#: assets/tasks.py:516 assets/tasks.py:530
msgid "Push system users to assets: {}"
msgstr "推送系统用户到入资产: {}"
#: assets/tasks.py:402
#: assets/tasks.py:522
msgid "Push system users to asset: {} => {}"
msgstr "推送系统用户到入资产: {} => {}"
#: assets/tasks.py:459
#: assets/tasks.py:600
msgid "Test asset user connectivity: {}"
msgstr "测试资产用户可连接性: {}"
......@@ -1347,7 +1364,7 @@ msgstr "启用MFA"
msgid "Import assets"
msgstr "导入资产"
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:54
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:53
#: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110
msgid "Asset list"
msgstr "资产列表"
......@@ -1382,7 +1399,7 @@ msgstr "需要二次认证来查看账号信息"
#: assets/templates/assets/_asset_user_view_auth_modal.html:20
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:211
#: assets/templates/assets/asset_detail.html:212
#: assets/templates/assets/asset_list.html:682
#: assets/templates/assets/cmd_filter_detail.html:106
#: assets/templates/assets/system_user_asset.html:112
......@@ -1434,7 +1451,6 @@ msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口"
#: assets/templates/assets/_system_user.html:37
#: assets/templates/assets/asset_create.html:16
#: assets/templates/assets/asset_update.html:21
#: assets/templates/assets/gateway_create_update.html:37
#: perms/templates/perms/asset_permission_create_update.html:38
#: perms/templates/perms/remote_app_permission_create_update.html:39
......@@ -1443,8 +1459,7 @@ msgid "Basic"
msgstr "基本"
#: assets/templates/assets/_system_user.html:44
#: assets/templates/assets/asset_create.html:26
#: assets/templates/assets/asset_update.html:31
#: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/gateway_create_update.html:45
#: users/templates/users/_user.html:21
msgid "Auth"
......@@ -1455,8 +1470,7 @@ msgid "Auto generate key"
msgstr "自动生成密钥"
#: assets/templates/assets/_system_user.html:69
#: assets/templates/assets/asset_create.html:60
#: assets/templates/assets/asset_update.html:64
#: assets/templates/assets/asset_create.html:74
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:53
#: perms/templates/perms/remote_app_permission_create_update.html:52
......@@ -1476,7 +1490,7 @@ msgstr "更新系统用户"
#: assets/templates/assets/_user_asset_detail_modal.html:11
#: assets/templates/assets/asset_asset_user_list.html:13
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:190
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:220
msgid "Asset detail"
msgstr "资产详情"
......@@ -1501,7 +1515,7 @@ msgstr "快速更新"
#: assets/templates/assets/admin_user_assets.html:70
#: assets/templates/assets/asset_asset_user_list.html:67
#: assets/templates/assets/asset_detail.html:179
#: assets/templates/assets/asset_detail.html:180
msgid "Test connective"
msgstr "测试可连接性"
......@@ -1509,7 +1523,7 @@ msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:118
#: assets/templates/assets/asset_asset_user_list.html:70
#: assets/templates/assets/asset_asset_user_list.html:119
#: assets/templates/assets/asset_detail.html:182
#: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/system_user_asset.html:75
#: assets/templates/assets/system_user_asset.html:166
#: assets/templates/assets/system_user_detail.html:151
......@@ -1530,7 +1544,7 @@ msgstr "查看认证"
#: assets/templates/assets/admin_user_assets.html:196
#: assets/templates/assets/asset_asset_user_list.html:162
#: assets/templates/assets/asset_detail.html:311
#: assets/templates/assets/asset_detail.html:312
#: assets/templates/assets/system_user_asset.html:353
#: users/templates/users/user_detail.html:307
#: users/templates/users/user_detail.html:334
......@@ -1617,7 +1631,7 @@ msgid "Please select file"
msgstr "选择文件"
#: assets/templates/assets/asset_asset_user_list.html:16
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:70
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:69
msgid "Asset user list"
msgstr "资产用户列表"
......@@ -1630,7 +1644,7 @@ msgid "Password version"
msgstr "密码版本"
#: assets/templates/assets/asset_asset_user_list.html:60
#: assets/templates/assets/asset_detail.html:148
#: assets/templates/assets/asset_detail.html:149
#: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:138
#: users/templates/users/user_profile.html:146
......@@ -1649,21 +1663,25 @@ msgstr "选择需要修改属性"
msgid "Select all"
msgstr "全选"
#: assets/templates/assets/asset_detail.html:96
#: assets/templates/assets/asset_create.html:24
msgid "Protocols"
msgstr "协议"
#: assets/templates/assets/asset_detail.html:97
msgid "CPU"
msgstr "CPU"
#: assets/templates/assets/asset_detail.html:104
#: assets/templates/assets/asset_detail.html:105
msgid "Disk"
msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:132
#: assets/templates/assets/asset_detail.html:133
#: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:104
msgid "Date joined"
msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:154
#: assets/templates/assets/asset_detail.html:155
#: assets/templates/assets/user_asset_list.html:46
#: perms/models/asset_permission.py:60 perms/models/base.py:38
#: perms/templates/perms/asset_permission_create_update.html:55
......@@ -1679,11 +1697,11 @@ msgstr "创建日期"
msgid "Active"
msgstr "激活中"
#: assets/templates/assets/asset_detail.html:171
#: assets/templates/assets/asset_detail.html:172
msgid "Refresh hardware"
msgstr "更新硬件信息"
#: assets/templates/assets/asset_detail.html:174
#: assets/templates/assets/asset_detail.html:175
msgid "Refresh"
msgstr "刷新"
......@@ -1696,7 +1714,7 @@ msgstr ""
"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,"
"右侧是属于该节点下的资产"
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:108
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:126
msgid "Create asset"
msgstr "创建资产"
......@@ -1830,7 +1848,7 @@ msgstr "删除"
msgid "Asset Deleting failed."
msgstr "删除失败"
#: assets/templates/assets/asset_update.html:60
#: assets/templates/assets/asset_update.html:7
msgid "Configuration"
msgstr "配置"
......@@ -2001,13 +2019,11 @@ msgstr ""
#: assets/templates/assets/system_user_list.html:12
msgid ""
"When system users are created, if you choose auto push Jumpserver to use "
"ansible push system users into the asset, if the asset (Switch, Windows) "
"does not support ansible, please manually fill in the account password. "
"Automatic push for Windows is not currently supported."
"ansible push system users into the asset, if the asset (Switch) does not "
"support ansible, please manually fill in the account password."
msgstr ""
"系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到"
"资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。目前还不"
"支持Windows的自动推送"
"资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。"
#: assets/templates/assets/system_user_list.html:43
#: assets/views/system_user.py:45
......@@ -2039,23 +2055,23 @@ msgstr "管理用户列表"
msgid "Admin user detail"
msgstr "管理用户详情"
#: assets/views/asset.py:81 templates/_nav_user.html:4
#: assets/views/asset.py:80 templates/_nav_user.html:4
msgid "My assets"
msgstr "我的资产"
#: assets/views/asset.py:122
#: assets/views/asset.py:141
msgid "Bulk update asset success"
msgstr "批量更新资产成功"
#: assets/views/asset.py:149
#: assets/views/asset.py:168
msgid "Bulk update asset"
msgstr "批量更新资产"
#: assets/views/asset.py:166
#: assets/views/asset.py:195
msgid "Update asset"
msgstr "更新资产"
#: assets/views/asset.py:307
#: assets/views/asset.py:337
msgid "already exists"
msgstr "已经存在"
......@@ -2223,7 +2239,7 @@ msgid "User agent"
msgstr "Agent"
#: audits/models.py:100 audits/templates/audits/login_log_list.html:57
#: xpack/plugins/change_auth_plan/models.py:413
#: xpack/plugins/change_auth_plan/models.py:417
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
#: xpack/plugins/cloud/models.py:172
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
......@@ -2249,8 +2265,8 @@ msgstr "登录日期"
#: perms/templates/perms/asset_permission_detail.html:86
#: perms/templates/perms/remote_app_permission_detail.html:78
#: terminal/models.py:165 terminal/templates/terminal/session_list.html:78
#: xpack/plugins/change_auth_plan/models.py:246
#: xpack/plugins/change_auth_plan/models.py:416
#: xpack/plugins/change_auth_plan/models.py:250
#: xpack/plugins/change_auth_plan/models.py:420
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
msgid "Date start"
......@@ -2603,18 +2619,35 @@ msgstr ""
msgid "Encrypt field using Secret Key"
msgstr ""
#: common/mixins.py:35
#: common/mixins.py:36
msgid "is discard"
msgstr ""
#: common/mixins.py:36
#: common/mixins.py:37
msgid "discard time"
msgstr ""
#: common/validators.py:7
#: common/mixins.py:210
#, python-format
msgid "%(name)s was %(action)s successfully"
msgstr "%(name)s %(action)s成功"
#: common/mixins.py:211
msgid "create"
msgstr "创建"
#: common/mixins.py:211
msgid "update"
msgstr "更新"
#: common/validators.py:11
msgid "Special char not allowed"
msgstr "不能包含特殊字符"
#: common/validators.py:23
msgid "This field must be unique."
msgstr "字段必须唯一"
#: jumpserver/views.py:185
msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
......@@ -2690,48 +2723,48 @@ msgstr "Become"
msgid "Create by"
msgstr "创建者"
#: ops/models/adhoc.py:223
#: ops/models/adhoc.py:224
msgid "{} Start task: {}"
msgstr "{} 任务开始: {}"
#: ops/models/adhoc.py:226
#: ops/models/adhoc.py:227
msgid "{} Task finish"
msgstr "{} 任务结束"
#: ops/models/adhoc.py:324
#: ops/models/adhoc.py:325
msgid "Start time"
msgstr "开始时间"
#: ops/models/adhoc.py:325
#: ops/models/adhoc.py:326
msgid "End time"
msgstr "完成时间"
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:33
#: xpack/plugins/change_auth_plan/models.py:249
#: xpack/plugins/change_auth_plan/models.py:419
#: xpack/plugins/change_auth_plan/models.py:253
#: xpack/plugins/change_auth_plan/models.py:423
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
msgid "Time"
msgstr "时间"
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_detail.html:106
#: ops/models/adhoc.py:328 ops/templates/ops/adhoc_detail.html:106
#: ops/templates/ops/adhoc_history.html:55
#: ops/templates/ops/adhoc_history_detail.html:69
#: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61
msgid "Is finished"
msgstr "是否完成"
#: ops/models/adhoc.py:328 ops/templates/ops/adhoc_history.html:56
#: ops/models/adhoc.py:329 ops/templates/ops/adhoc_history.html:56
#: ops/templates/ops/task_history.html:62
msgid "Is success"
msgstr "是否成功"
#: ops/models/adhoc.py:329
#: ops/models/adhoc.py:330
msgid "Adhoc raw result"
msgstr "结果"
#: ops/models/adhoc.py:330
#: ops/models/adhoc.py:331
msgid "Adhoc result summary"
msgstr "汇总"
......@@ -2859,33 +2892,33 @@ msgstr "任务列表"
msgid "Go"
msgstr ""
#: ops/templates/ops/command_execution_create.html:152
#: ops/templates/ops/command_execution_create.html:155
msgid "Selected assets"
msgstr "已选择资产"
#: ops/templates/ops/command_execution_create.html:155
#: ops/templates/ops/command_execution_create.html:158
msgid "In total"
msgstr "总共"
#: ops/templates/ops/command_execution_create.html:190
#: ops/templates/ops/command_execution_create.html:193
msgid ""
"Select the left asset, select the running system user, execute command in "
"batch"
msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令"
#: ops/templates/ops/command_execution_create.html:208
#: ops/templates/ops/command_execution_create.html:211
msgid "Unselected assets"
msgstr "没有选中资产"
#: ops/templates/ops/command_execution_create.html:212
#: ops/templates/ops/command_execution_create.html:215
msgid "No input command"
msgstr "没有输入命令"
#: ops/templates/ops/command_execution_create.html:216
#: ops/templates/ops/command_execution_create.html:219
msgid "No system user was selected"
msgstr "没有选择系统用户"
#: ops/templates/ops/command_execution_create.html:261
#: ops/templates/ops/command_execution_create.html:264
msgid "Pending"
msgstr "等待"
......@@ -2966,7 +2999,7 @@ msgstr "命令执行列表"
msgid "Command execution"
msgstr "命令执行"
#: orgs/mixins.py:81 orgs/models.py:24
#: orgs/mixins.py:83 orgs/models.py:24
msgid "Organization"
msgstr "组织管理"
......@@ -3167,12 +3200,12 @@ msgstr "添加用户组"
#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:65
#: perms/views/asset_permission.py:80 perms/views/asset_permission.py:95
#: perms/views/asset_permission.py:130 perms/views/asset_permission.py:162
#: perms/views/remote_app_permission.py:33
#: perms/views/remote_app_permission.py:48
#: perms/views/remote_app_permission.py:63
#: perms/views/remote_app_permission.py:76
#: perms/views/remote_app_permission.py:102
#: perms/views/remote_app_permission.py:138 templates/_nav.html:41
#: perms/views/remote_app_permission.py:32
#: perms/views/remote_app_permission.py:47
#: perms/views/remote_app_permission.py:62
#: perms/views/remote_app_permission.py:75
#: perms/views/remote_app_permission.py:101
#: perms/views/remote_app_permission.py:137 templates/_nav.html:41
#: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Perms"
msgstr "权限管理"
......@@ -3201,27 +3234,27 @@ msgstr "资产授权用户列表"
msgid "Asset permission asset list"
msgstr "资产授权资产列表"
#: perms/views/remote_app_permission.py:34
#: perms/views/remote_app_permission.py:33
msgid "RemoteApp permission list"
msgstr "远程应用授权列表"
#: perms/views/remote_app_permission.py:49
#: perms/views/remote_app_permission.py:48
msgid "Create RemoteApp permission"
msgstr "创建远程应用授权规则"
#: perms/views/remote_app_permission.py:64
#: perms/views/remote_app_permission.py:63
msgid "Update RemoteApp permission"
msgstr "更新远程应用授权规则"
#: perms/views/remote_app_permission.py:77
#: perms/views/remote_app_permission.py:76
msgid "RemoteApp permission detail"
msgstr "远程应用授权详情"
#: perms/views/remote_app_permission.py:103
#: perms/views/remote_app_permission.py:102
msgid "RemoteApp permission user list"
msgstr "远程应用授权用户列表"
#: perms/views/remote_app_permission.py:139
#: perms/views/remote_app_permission.py:138
msgid "RemoteApp permission RemoteApp list"
msgstr "远程应用授权远程应用列表"
......@@ -4380,7 +4413,7 @@ msgstr "生成重置密码链接,通过邮件发送给用户"
msgid "Set password"
msgstr "设置密码"
#: users/forms.py:117 xpack/plugins/change_auth_plan/models.py:83
#: users/forms.py:117 xpack/plugins/change_auth_plan/models.py:86
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
......@@ -5284,23 +5317,17 @@ msgstr "定时执行"
#: xpack/plugins/change_auth_plan/forms.py:120
msgid ""
"Tips: Currently only unix-like assets are supported, while Windows assets "
"are not"
msgstr "提示:目前仅支持类Unix资产,暂不支持Windows资产"
#: xpack/plugins/change_auth_plan/forms.py:122
msgid ""
"Tips: The username of the user on the asset to be modified. if the user "
"exists, change the password; If the user does not exist, create the user."
msgstr ""
"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果"
"用户不存在,则创建用户。"
#: xpack/plugins/change_auth_plan/forms.py:126
#: xpack/plugins/change_auth_plan/forms.py:124
msgid "Tips: (Units: hour)"
msgstr "提示:(单位: 时)"
#: xpack/plugins/change_auth_plan/forms.py:127
#: xpack/plugins/change_auth_plan/forms.py:125
msgid ""
"eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux "
"crontab expressions <min hour day month week> (<a href='https://tool.lu/"
......@@ -5312,8 +5339,8 @@ msgstr ""
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
#: xpack/plugins/change_auth_plan/meta.py:9
#: xpack/plugins/change_auth_plan/models.py:111
#: xpack/plugins/change_auth_plan/models.py:253
#: xpack/plugins/change_auth_plan/models.py:114
#: xpack/plugins/change_auth_plan/models.py:257
#: xpack/plugins/change_auth_plan/views.py:31
#: xpack/plugins/change_auth_plan/views.py:47
#: xpack/plugins/change_auth_plan/views.py:68
......@@ -5324,61 +5351,61 @@ msgstr ""
msgid "Change auth plan"
msgstr "改密计划"
#: xpack/plugins/change_auth_plan/models.py:52
#: xpack/plugins/change_auth_plan/models.py:55
msgid "Custom password"
msgstr "自定义密码"
#: xpack/plugins/change_auth_plan/models.py:53
#: xpack/plugins/change_auth_plan/models.py:56
msgid "All assets use the same random password"
msgstr "所有资产使用相同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:54
#: xpack/plugins/change_auth_plan/models.py:57
msgid "All assets use different random password"
msgstr "所有资产使用不同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:73
#: xpack/plugins/change_auth_plan/models.py:142
#: xpack/plugins/change_auth_plan/models.py:76
#: xpack/plugins/change_auth_plan/models.py:145
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
msgid "Cycle perform"
msgstr "周期执行"
#: xpack/plugins/change_auth_plan/models.py:78
#: xpack/plugins/change_auth_plan/models.py:140
#: xpack/plugins/change_auth_plan/models.py:81
#: xpack/plugins/change_auth_plan/models.py:143
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
msgid "Regularly perform"
msgstr "定期执行"
#: xpack/plugins/change_auth_plan/models.py:87
#: xpack/plugins/change_auth_plan/models.py:90
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
msgid "Password rules"
msgstr "密码规则"
#: xpack/plugins/change_auth_plan/models.py:210
msgid "For security, do not change root user's password"
msgstr "为了安全,禁止更改root用户的密码"
#: xpack/plugins/change_auth_plan/models.py:213
msgid "For security, do not change {} user's password"
msgstr "为了安全,禁止更改 {} 用户的密码"
#: xpack/plugins/change_auth_plan/models.py:217
msgid "Assets is empty, please add the asset"
msgstr "资产为空,请添加资产"
#: xpack/plugins/change_auth_plan/models.py:257
#: xpack/plugins/change_auth_plan/models.py:261
msgid "Change auth plan snapshot"
msgstr "改密计划快照"
#: xpack/plugins/change_auth_plan/models.py:272
#: xpack/plugins/change_auth_plan/models.py:423
#: xpack/plugins/change_auth_plan/models.py:276
#: xpack/plugins/change_auth_plan/models.py:427
msgid "Change auth plan execution"
msgstr "改密计划执行"
#: xpack/plugins/change_auth_plan/models.py:432
#: xpack/plugins/change_auth_plan/models.py:436
msgid "Change auth plan execution subtask"
msgstr "改密计划执行子任务"
#: xpack/plugins/change_auth_plan/models.py:450
#: xpack/plugins/change_auth_plan/models.py:454
msgid "Authentication failed"
msgstr "认证失败"
#: xpack/plugins/change_auth_plan/models.py:452
#: xpack/plugins/change_auth_plan/models.py:456
msgid "Connection timeout"
msgstr "连接超时"
......
......@@ -21,7 +21,7 @@ class JMSBaseInventory(BaseInventory):
'id': asset.id,
'hostname': asset.hostname,
'ip': asset.ip,
'port': asset.port,
'port': asset.ssh_port,
'vars': dict(),
'groups': [],
}
......@@ -29,8 +29,15 @@ class JMSBaseInventory(BaseInventory):
info["vars"].update(self.make_proxy_command(asset))
if run_as_admin:
info.update(asset.get_auth_info())
if asset.is_unixlike():
info["become"] = asset.admin_user.become_info
for node in asset.nodes.all():
info["groups"].append(node.value)
if asset.is_windows():
info["vars"].update({
"ansible_connection": "ssh",
"ansible_shell_type": "cmd",
})
for label in asset.labels.all():
info["vars"].update({
label.name: label.value
......@@ -73,7 +80,7 @@ class JMSInventory(JMSBaseInventory):
"""
def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None):
"""
:param host_id_list: ["test1", ]
:param assets: assets
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
:param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username)
:param become_info: 是否become成某个用户去执行
......@@ -86,17 +93,14 @@ class JMSInventory(JMSBaseInventory):
host_list = []
for asset in assets:
info = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
host_list.append(info)
if run_as:
for host in host_list:
host = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
if run_as:
run_user_info = self.get_run_user_info(host)
host.update(run_user_info)
if become_info:
for host in host_list:
if become_info and asset.is_unixlike():
host.update(become_info)
host_list.append(host)
super().__init__(host_list=host_list)
def get_run_user_info(self, host):
......@@ -133,12 +137,10 @@ class JMSCustomInventory(JMSBaseInventory):
host_list = []
for asset in assets:
info = self.convert_to_ansible(asset)
host_list.append(info)
for host in host_list:
host = self.convert_to_ansible(asset)
run_user_info = self.get_run_user_info()
host.update(run_user_info)
host_list.append(host)
super().__init__(host_list=host_list)
......
......@@ -220,6 +220,7 @@ class AdHoc(models.Model):
time_start = time.time()
try:
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
history.date_start = date_start
print(_("{} Start task: {}").format(date_start, self.task.name))
raw, summary = self._run_only()
date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
......
......@@ -134,8 +134,11 @@ function getSelectedAssetsNode() {
var assetsNodeId = [];
var assetsNode = [];
nodes.forEach(function (node) {
if (node.meta.type === 'asset' && !node.isHidden && node.meta.asset.protocol === 'ssh') {
if (assetsNodeId.indexOf(node.id) === -1) {
if (node.meta.type === 'asset' && !node.isHidden) {
var protocols = $.map(node.meta.asset.protocols, function (v) {
return v.name
});
if (assetsNodeId.indexOf(node.id) === -1 && protocols.indexOf("ssh") > -1) {
assetsNodeId.push(node.id);
assetsNode.push(node)
}
......
......@@ -18,7 +18,7 @@ def update_or_create_ansible_task(
run_as_admin=False, run_as=None, become_info=None,
):
if not hosts or not tasks or not task_name:
return
return None, None
set_to_root_org()
defaults = {
'name': task_name,
......
......@@ -9,8 +9,10 @@ from django.forms import ModelForm
from django.http.response import HttpResponseForbidden
from django.core.exceptions import ValidationError
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from common.utils import get_logger
from common.validators import ProjectUniqueValidator
from .utils import (
current_org, set_current_org, set_to_root_org, get_current_org_id
)
......@@ -216,3 +218,14 @@ class OrgResourceSerializerMixin(serializers.Serializer):
但是coco需要资产的org_id字段,所以修改为CharField类型
"""
org_id = serializers.CharField(default=get_current_org_id)
def get_validators(self):
_validators = super().get_validators()
validators = []
for v in _validators:
if isinstance(v, UniqueTogetherValidator) \
and "org_id" in v.fields:
v = ProjectUniqueValidator(v.queryset, v.fields)
validators.append(v)
return validators
......@@ -154,7 +154,7 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
assets = util.get_assets()
for k, v in assets.items():
system_users_granted = [s for s in v if s.protocol == k.protocol]
system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
k.system_users_granted = system_users_granted
queryset.append(k)
return queryset
......@@ -215,8 +215,7 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
for node, _assets in nodes.items():
assets = _assets.keys()
for k, v in _assets.items():
system_users_granted = [s for s in v if
s.protocol == k.protocol]
system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
k.system_users_granted = system_users_granted
node.assets_granted = assets
queryset.append(node)
......@@ -364,7 +363,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
for asset, system_users in nodes_granted[node].items():
fake_node = asset.as_node()
fake_node.assets_amount = 0
system_users = [s for s in system_users if s.protocol == asset.protocol]
system_users = [s for s in system_users if asset.has_protocol(s.protocol)]
fake_node.asset.system_users_granted = system_users
fake_node.key = node.key + ':0'
fake_nodes.append(fake_node)
......@@ -389,7 +388,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
fake_node = asset.as_node()
fake_node.assets_amount = 0
system_users = [s for s in system_users if
s.protocol == asset.protocol]
asset.has_protocol(s.protocol)]
fake_node.asset.system_users_granted = system_users
fake_node.key = node.key + ':0'
matched_assets.append(fake_node)
......
......@@ -3,11 +3,12 @@
from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node, RemoteApp
from assets.models import Asset, SystemUser, Node
from assets.serializers import (
AssetGrantedSerializer, NodeSerializer
)
from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp
......@@ -238,7 +238,7 @@ class AssetPermissionUtil:
for perm in permissions:
actions = perm.actions.all()
for asset in perm.assets.all().valid().prefetch_related('nodes'):
system_users = perm.system_users.filter(protocol=asset.protocol)
system_users = perm.system_users.filter(protocol__in=asset.protocols_name)
system_users = self._structured_system_user(system_users, actions)
assets[asset].update(system_users)
return assets
......@@ -255,7 +255,7 @@ class AssetPermissionUtil:
_assets = node.get_all_assets().valid().prefetch_related('nodes')
for asset in _assets:
for system_user, attr_dict in system_users.items():
if system_user.protocol != asset.protocol:
if not asset.has_protocol(system_user.protocol):
continue
if system_user in assets[asset]:
actions = assets[asset][system_user]['actions']
......@@ -279,15 +279,12 @@ class AssetPermissionUtil:
resource=resource
)
@property
def node_key(self):
return self.get_cache_key('NODES_WITH_ASSETS')
@property
def asset_key(self):
return self.get_cache_key('ASSETS')
@property
def system_key(self):
return self.get_cache_key('SYSTEM_USER')
......@@ -457,7 +454,7 @@ def parse_node_to_tree_node(node):
def parse_asset_to_tree_node(node, asset, system_users):
system_users_protocol_matched = [s for s in system_users if s.protocol == asset.protocol]
system_users_protocol_matched = [s for s in system_users if asset.has_protocol(s.protocol)]
icon_skin = 'file'
if asset.platform.lower() == 'windows':
icon_skin = 'windows'
......@@ -490,8 +487,8 @@ def parse_asset_to_tree_node(node, asset, system_users):
'id': asset.id,
'hostname': asset.hostname,
'ip': asset.ip,
'port': asset.port,
'protocol': asset.protocol,
'protocols': [{"name": p.name, "port": p.port}
for p in asset.protocols.all()],
'platform': asset.platform,
'domain': None if not asset.domain else asset.domain.id,
'is_active': asset.is_active,
......
......@@ -11,9 +11,8 @@ from django.conf import settings
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from users.models import UserGroup
from assets.models import RemoteApp
from ..hands import RemoteApp, UserGroup
from ..models import RemoteAppPermission
from ..forms import RemoteAppPermissionCreateUpdateForm
......
......@@ -453,4 +453,16 @@ div.dataTables_wrapper div.dataTables_filter {
#tree-refresh .fa-refresh {
font: normal normal normal 14px/1 FontAwesome !important;
}
\ No newline at end of file
}
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single, .select2-selection__arrow {
height: 34px !important;
}
.select2-selection {
border-radius: 0 !important;
}
span.select2-selection__placeholder {
line-height: 34px !important;
}
......@@ -165,11 +165,13 @@ function formSubmit(props) {
/*
{
"form": $("form"),
"data": {},
"url": "",
"method": "POST",
"redirect_to": "",
"success": function(data, textStatue, jqXHR){},
"error": function(jqXHR, textStatus, errorThrown) {}
"error": function(jqXHR, textStatus, errorThrown) {},
"message": "",
}
*/
props = props || {};
......@@ -183,6 +185,10 @@ function formSubmit(props) {
dataType: props.data_type || "json"
}).done(function (data, textState, jqXHR) {
if (redirect_to) {
if (props.message) {
var messages="ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
setCookie("messages", messages)
}
location.href = redirect_to;
} else if (typeof props.success === 'function') {
return props.success(data, textState, jqXHR);
......@@ -230,7 +236,15 @@ function formSubmit(props) {
var help_msg = v.join("<br/>") ;
helpBlockRef.html(help_msg);
} else {
noneFieldErrorMsg += v + '<br/>';
$.each(v, function (kk, vv) {
if (typeof errors === "object") {
$.each(vv, function (kkk, vvv) {
noneFieldErrorMsg += " " + vvv + '<br/>';
})
} else{
noneFieldErrorMsg += vv + '<br/>';
}
})
}
});
if (noneFieldErrorRef.length === 1 && noneFieldErrorMsg !== '') {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment