diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py
index 39182fbf6..a89698c66 100644
--- a/apps/assets/api/asset.py
+++ b/apps/assets/api/asset.py
@@ -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:
diff --git a/apps/assets/const.py b/apps/assets/const.py
index 0cd74ba0d..1f93482b6 100644
--- a/apps/assets/const.py
+++ b/apps/assets/const.py
@@ -1,8 +1,6 @@
# -*- 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 = {
diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py
index 774867a6b..e172ab8ea 100644
--- a/apps/assets/forms/asset.py
+++ b/apps/assets/forms/asset.py
@@ -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(
diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py
index e832ab158..5c7d3c770 100644
--- a/apps/assets/forms/user.py
+++ b/apps/assets/forms/user.py
@@ -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)
diff --git a/apps/assets/migrations/0027_auto_20190521_1703.py b/apps/assets/migrations/0027_auto_20190521_1703.py
index da38b1791..91446019f 100644
--- a/apps/assets/migrations/0027_auto_20190521_1703.py
+++ b/apps/assets/migrations/0027_auto_20190521_1703.py
@@ -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'),
+ ),
]
diff --git a/apps/assets/migrations/0028_protocol.py b/apps/assets/migrations/0028_protocol.py
new file mode 100644
index 000000000..230c43773
--- /dev/null
+++ b/apps/assets/migrations/0028_protocol.py
@@ -0,0 +1,29 @@
+# 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'),
+ ),
+ ]
diff --git a/apps/assets/migrations/0029_auto_20190522_1114.py b/apps/assets/migrations/0029_auto_20190522_1114.py
new file mode 100644
index 000000000..e1d20bb5f
--- /dev/null
+++ b/apps/assets/migrations/0029_auto_20190522_1114.py
@@ -0,0 +1,22 @@
+# 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),
+ ]
diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py
index c82c66a69..b87a18796 100644
--- a/apps/assets/models/__init__.py
+++ b/apps/assets/models/__init__.py
@@ -8,4 +8,3 @@ from .asset import *
from .cmd_filter import *
from .utils import *
from .authbook import *
-from applications.models.remote_app import *
diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py
index 47a34243d..9512d26ba 100644
--- a/apps/assets/models/asset.py
+++ b/apps/assets/models/asset.py
@@ -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,6 +48,35 @@ class AssetQuerySet(models.QuerySet):
return self.active()
+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'
+ PROTOCOL_VNC = 'vnc'
+ PROTOCOL_CHOICES = (
+ (PROTOCOL_SSH, 'ssh'),
+ (PROTOCOL_RDP, 'rdp'),
+ (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 = (
@@ -59,22 +89,15 @@ class Asset(OrgModelMixin):
('Other', 'Other'),
)
- PROTOCOL_SSH = 'ssh'
- PROTOCOL_RDP = 'rdp'
- PROTOCOL_TELNET = 'telnet'
- PROTOCOL_VNC = 'vnc'
- PROTOCOL_CHOICES = (
- (PROTOCOL_SSH, 'ssh'),
- (PROTOCOL_RDP, 'rdp'),
- (PROTOCOL_TELNET, 'telnet (beta)'),
- (PROTOCOL_VNC, 'vnc'),
- )
-
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':
- return False
- return True
+ @property
+ def protocols_name(self):
+ names = []
+ for protocol in self.protocols.all():
+ names.append(protocol.name)
+ return names
- def is_unixlike(self):
- if self.platform not in ("Windows", "Windows2016"):
+ 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
+ def is_unixlike(self):
+ 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:
diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py
index 30d8ae0f5..40336fd00 100644
--- a/apps/assets/models/base.py
+++ b/apps/assets/models/base.py
@@ -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 {
diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py
index 1e661d631..ecd9ae429 100644
--- a/apps/assets/models/user.py
+++ b/apps/assets/models/user.py
@@ -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
diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py
index 3e1cf39bb..9ac3ba725 100644
--- a/apps/assets/serializers/asset.py
+++ b/apps/assets/serializers/asset.py
@@ -1,13 +1,14 @@
# -*- 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
diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py
index 4afb97e75..23e923a3e 100644
--- a/apps/assets/signals_handler.py
+++ b/apps/assets/signals_handler.py
@@ -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)
diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py
index 172175f38..61f0d29ec 100644
--- a/apps/assets/tasks.py
+++ b/apps/assets/tasks.py
@@ -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,
diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html
index 3c9cf1221..71c3420d0 100644
--- a/apps/assets/templates/assets/_system_user.html
+++ b/apps/assets/templates/assets/_system_user.html
@@ -95,43 +95,99 @@ 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') {
- $('.auth-fields').removeClass('hidden');
+ 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');
- $.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 === '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');
+ $(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');
- $.each(need_change_field, function (index, value) {
- $(value).closest('.form-group').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');
+ $(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() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
@@ -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();
})
diff --git a/apps/assets/templates/assets/asset_asset_user_list.html b/apps/assets/templates/assets/asset_asset_user_list.html
index ef192d790..b5a99efea 100644
--- a/apps/assets/templates/assets/asset_asset_user_list.html
+++ b/apps/assets/templates/assets/asset_asset_user_list.html
@@ -62,7 +62,7 @@
- {% if asset.protocol == 'ssh' %}
+ {% if asset.is_support_ansible %}
{% trans 'Test connective' %}: |
@@ -118,7 +118,7 @@ function initAssetUserTable() {
var view_btn = ' {% trans "View auth" %}'.replace("DEFAULT_USERNAME", cellData);
var test_btn = ' {% trans "Test" %}'.replace("DEFAULT_USERNAME", cellData);
btn += view_btn;
- {% if asset.protocol == 'ssh' %}
+ {% if asset.is_support_ansible %}
btn += test_btn;
{% endif %}
$(td).html(btn);
diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html
index 7a32264cc..1d04f2bec 100644
--- a/apps/assets/templates/assets/asset_create.html
+++ b/apps/assets/templates/assets/asset_create.html
@@ -16,12 +16,24 @@
{% trans 'Basic' %}
{% 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" %}
+
+ {% trans 'Protocols' %}
+
+ {% for fm in formset.forms %}
+
+ {% endfor %}
+
{% trans 'Auth' %}
{% bootstrap_field form.admin_user layout="horizontal" %}
@@ -55,6 +67,8 @@
{% endif %}
+ {% block extra %}
+ {% endblock %}
{% trans 'Other' %}
@@ -73,11 +87,25 @@
{% block custom_foot_js %}
+ {% block form_submit %}
+
+ {% endblock %}
{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html
index 01c048af8..e89b56283 100644
--- a/apps/assets/templates/assets/asset_detail.html
+++ b/apps/assets/templates/assets/asset_detail.html
@@ -69,12 +69,13 @@
| {{ asset.public_ip|default:"" }} |
- {% trans 'Port' %}: |
- {{ asset.port }} |
-
-
- {% trans 'Protocol' %}: |
- {{ asset.protocol }} |
+ {% trans 'Protocol' %} |
+
+ {% for protocol in asset.protocols.all %}
+ {{ protocol.name }}:
+ {{ protocol.port }}
+ {% endfor %}
+ |
{% trans 'Admin user' %}: |
@@ -94,7 +95,7 @@
{% trans 'CPU' %}: |
- {{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }} |
+ {{ asset.cpu_info }} |
{% trans 'Memory' %}: |
@@ -166,7 +167,7 @@
- {% if asset.protocol == 'ssh' %}
+ {% if asset.is_support_ansible %}
{% trans 'Refresh hardware' %}: |
diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html
index caff4286d..8e59c097b 100644
--- a/apps/assets/templates/assets/asset_update.html
+++ b/apps/assets/templates/assets/asset_update.html
@@ -1,95 +1,51 @@
-{% 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 %}
-
-
-{% endblock %}
-
-{% block form %}
-
{% endblock %}
-{% block custom_foot_js %}
-
+ .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);
+ });
+
{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html
index 26e9bbef0..55f625d81 100644
--- a/apps/assets/templates/assets/system_user_detail.html
+++ b/apps/assets/templates/assets/system_user_detail.html
@@ -118,7 +118,7 @@
-
+
{% trans 'Auto push' %}: |
@@ -135,7 +135,7 @@
|
{% if system_user.auto_push %}
-
+
{% trans 'Push system user now' %}: |
@@ -144,7 +144,7 @@
|
{% endif %}
-
+
{% trans 'Test assets connective' %}: |
@@ -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()
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html
index 2b1a63115..0744b793b 100644
--- a/apps/assets/templates/assets/system_user_list.html
+++ b/apps/assets/templates/assets/system_user_list.html
@@ -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.' %}
{% endblock %}
diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py
index 4a3ce2273..c884b4902 100644
--- a/apps/assets/views/asset.py
+++ b/apps/assets/views/asset.py
@@ -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)
diff --git a/apps/common/decorator.py b/apps/common/decorator.py
new file mode 100644
index 000000000..6edc4f3c3
--- /dev/null
+++ b/apps/common/decorator.py
@@ -0,0 +1,12 @@
+# -*- 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
diff --git a/apps/common/mixins.py b/apps/common/mixins.py
index 8e4af26dd..12f147689 100644
--- a/apps/common/mixins.py
+++ b/apps/common/mixins.py
@@ -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
diff --git a/apps/common/validators.py b/apps/common/validators.py
index b273bd1de..8106f27bd 100644
--- a/apps/common/validators.py
+++ b/apps/common/validators.py
@@ -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)
diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py
index 28ac12456..0362a68e3 100644
--- a/apps/jumpserver/conf.py
+++ b/apps/jumpserver/conf.py
@@ -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
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index 917c82326..1c782fa73 100644
Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index de1d9f770..899cd30b7 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -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 \n"
"Language-Team: Jumpserver team\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
-msgid ""
-"Push system user task skip, auto push not enable or protocol is not ssh: {}"
-msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}"
+#: 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:396 assets/tasks.py:410
+#: assets/tasks.py:481
+msgid ""
+"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: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 ""
"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> Tips: Using 5 digits linux "
"crontab expressions ( -1) {
assetsNodeId.push(node.id);
assetsNode.push(node)
}
diff --git a/apps/ops/utils.py b/apps/ops/utils.py
index 4a7def4fa..70cba27ba 100644
--- a/apps/ops/utils.py
+++ b/apps/ops/utils.py
@@ -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,
diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py
index f41ffccbe..f6edd545e 100644
--- a/apps/orgs/mixins.py
+++ b/apps/orgs/mixins.py
@@ -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
diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py
index b4f8fc07e..1445ccac6 100644
--- a/apps/perms/api/user_permission.py
+++ b/apps/perms/api/user_permission.py
@@ -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)
diff --git a/apps/perms/hands.py b/apps/perms/hands.py
index 2a208fefa..5f3aafd84 100644
--- a/apps/perms/hands.py
+++ b/apps/perms/hands.py
@@ -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
diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py
index ed9f8f1a3..924f36675 100644
--- a/apps/perms/utils/asset_permission.py
+++ b/apps/perms/utils/asset_permission.py
@@ -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,
diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py
index 660d43ccd..2ab95091f 100644
--- a/apps/perms/views/remote_app_permission.py
+++ b/apps/perms/views/remote_app_permission.py
@@ -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
diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css
index c3dcfb386..b1f2633d8 100644
--- a/apps/static/css/jumpserver.css
+++ b/apps/static/css/jumpserver.css
@@ -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;
+}
diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js
index 0197312fd..c3805920b 100644
--- a/apps/static/js/jumpserver.js
+++ b/apps/static/js/jumpserver.js
@@ -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(" ") ;
helpBlockRef.html(help_msg);
} else {
- noneFieldErrorMsg += v + ' ';
+ $.each(v, function (kk, vv) {
+ if (typeof errors === "object") {
+ $.each(vv, function (kkk, vvv) {
+ noneFieldErrorMsg += " " + vvv + ' ';
+ })
+ } else{
+ noneFieldErrorMsg += vv + ' ';
+ }
+ })
}
});
if (noneFieldErrorRef.length === 1 && noneFieldErrorMsg !== '') {
| |