mirror of https://github.com/jumpserver/jumpserver
Dev ansible windows 2 (#2783)
* [Update] 改密支持windows * [Update] 修改asset表结构 * [Feature] Windows支持批量改密、测试可连接性等功能 * [Update] 处理创建资产时labels的问题 * [Update] 优化测试管理系统、系统用户可连接性任务执行逻辑 * [Update] 优化ansible任务逻辑;添加自动推送rdp系统用户功能 * [Update] 添加翻译 * [Update] 优化ansible任务逻辑(测试系统用户可连接性, 通过协议过滤资产) * [Update] 更新翻译 * [Update] 更新翻译 * [Update] 推送windows系统用户,默认添加到Users、Remote Desktop Users组中 * [Update] 优化小细节 * [Update] 更新翻译,删除多余代码 * [Update] 更新翻译信息pull/2794/head
parent
9f9f22548f
commit
ddafd7ba26
|
@ -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:
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _
|
|||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
from ..models import Asset, AdminUser, Protocol
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
|
||||
__all__ = [
|
||||
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm',
|
||||
'ProtocolForm'
|
||||
]
|
||||
|
||||
|
||||
class ProtocolForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Protocol
|
||||
fields = ['name', 'port']
|
||||
widgets = {
|
||||
'name': forms.Select(attrs={
|
||||
'class': 'form-control protocol-name'
|
||||
}),
|
||||
'port': forms.TextInput(attrs={
|
||||
'class': 'form-control protocol-port'
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
'domain', 'protocol',
|
||||
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
|
@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm):
|
|||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
|
@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm):
|
|||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
|
||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||
'domain', 'protocol',
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
|
@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm):
|
|||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
|
@ -101,8 +117,8 @@ class AssetBulkUpdateForm(OrgModelForm):
|
|||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'admin_user', 'labels', 'platform',
|
||||
'protocol', 'domain',
|
||||
'assets', 'admin_user', 'labels', 'platform',
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'labels': forms.SelectMultiple(
|
||||
|
|
|
@ -100,16 +100,18 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
|||
private_key, public_key = super().gen_keys()
|
||||
|
||||
if login_mode == SystemUser.LOGIN_MANUAL or \
|
||||
protocol in [SystemUser.PROTOCOL_RDP,
|
||||
SystemUser.PROTOCOL_TELNET,
|
||||
protocol in [SystemUser.PROTOCOL_TELNET,
|
||||
SystemUser.PROTOCOL_VNC]:
|
||||
system_user.auto_push = 0
|
||||
auto_generate_key = False
|
||||
system_user.save()
|
||||
auto_generate_key = False
|
||||
|
||||
if auto_generate_key:
|
||||
logger.info('Auto generate key and set system user auth')
|
||||
system_user.auto_gen_auth()
|
||||
if protocol == SystemUser.PROTOCOL_SSH:
|
||||
system_user.auto_gen_auth()
|
||||
elif protocol == SystemUser.PROTOCOL_RDP:
|
||||
system_user.auto_gen_auth_password()
|
||||
else:
|
||||
system_user.set_auth(password=password, private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
|
|
@ -15,4 +15,9 @@ class Migration(migrations.Migration):
|
|||
name='ip',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='public_ip',
|
||||
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Public IP'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
]
|
|
@ -8,4 +8,3 @@ from .asset import *
|
|||
from .cmd_filter import *
|
||||
from .utils import *
|
||||
from .authbook import *
|
||||
from applications.models.remote_app import *
|
||||
|
|
|
@ -12,11 +12,12 @@ from django.db import models
|
|||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from .user import AdminUser, SystemUser
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = ['Asset']
|
||||
__all__ = ['Asset', 'Protocol']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -47,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:
|
||||
|
|
|
@ -158,6 +158,10 @@ class AssetUser(OrgModelMixin):
|
|||
private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
||||
def auto_gen_auth_password(self):
|
||||
password = str(uuid.uuid4())
|
||||
self.set_auth(password=password)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
return {
|
||||
|
|
|
@ -201,7 +201,7 @@ class SystemUser(AssetUser):
|
|||
return self.get_login_mode_display()
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
|
||||
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db.models.signals import post_save, m2m_changed, post_delete
|
|||
from django.dispatch import receiver
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.decorator import on_transaction_commit
|
||||
from .models import Asset, SystemUser, Node, AuthBook
|
||||
from .tasks import (
|
||||
update_assets_hardware_info_util,
|
||||
|
@ -32,9 +33,12 @@ def set_asset_root_node(asset):
|
|||
|
||||
|
||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
@on_transaction_commit
|
||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
if created:
|
||||
logger.info("Asset `{}` create signal received".format(instance))
|
||||
|
||||
# 获取资产硬件信息
|
||||
update_asset_hardware_info_on_created(instance)
|
||||
test_asset_conn_on_created(instance)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
import re
|
||||
import os
|
||||
|
||||
from collections import defaultdict
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.cache import cache
|
||||
|
@ -31,7 +32,7 @@ def check_asset_can_run_ansible(asset):
|
|||
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
return False
|
||||
if not asset.support_ansible():
|
||||
if not asset.is_support_ansible():
|
||||
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
return False
|
||||
|
@ -45,10 +46,21 @@ def clean_hosts(assets):
|
|||
continue
|
||||
clean_assets.append(asset)
|
||||
if not clean_assets:
|
||||
print(_("No assets matched, stop task"))
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return clean_assets
|
||||
|
||||
|
||||
def clean_hosts_by_protocol(system_user, assets):
|
||||
hosts = [
|
||||
asset for asset in assets
|
||||
if asset.has_protocol(system_user.protocol)
|
||||
]
|
||||
if not hosts:
|
||||
msg = _("No assets matched related system user protocol, stop task")
|
||||
logger.info(msg)
|
||||
return hosts
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_assets_hardware_info(assets, result, **kwargs):
|
||||
"""
|
||||
|
@ -96,7 +108,7 @@ def set_assets_hardware_info(assets, result, **kwargs):
|
|||
___disk_total = '%s %s' % sum_capacity(disk_info.values())
|
||||
___disk_info = json.dumps(disk_info)
|
||||
|
||||
___platform = info.get('ansible_system', 'Unknown')
|
||||
# ___platform = info.get('ansible_system', 'Unknown')
|
||||
___os = info.get('ansible_distribution', 'Unknown')
|
||||
___os_version = info.get('ansible_distribution_version', 'Unknown')
|
||||
___os_arch = info.get('ansible_architecture', 'Unknown')
|
||||
|
@ -163,25 +175,53 @@ def test_asset_connectivity_util(assets, task_name=None):
|
|||
|
||||
if task_name is None:
|
||||
task_name = _("Test assets connectivity")
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
created_by = assets[0].org_id
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
|
||||
|
||||
hosts_category = {
|
||||
'linux': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_ADMIN_USER_CONN_TASKS
|
||||
},
|
||||
'windows': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_TASKS
|
||||
}
|
||||
}
|
||||
for host in hosts:
|
||||
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
|
||||
else hosts_category['linux']['hosts']
|
||||
hosts_list.append(host)
|
||||
|
||||
results_summary = dict(
|
||||
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
created_by = assets[0].org_id
|
||||
for _, value in hosts_category.items():
|
||||
if not value['hosts']:
|
||||
continue
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
|
||||
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
created_by=created_by,
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
results_summary['success'] &= summary['success']
|
||||
results_summary['contacted'].update(summary['contacted'])
|
||||
results_summary['dark'].update(summary['dark'])
|
||||
|
||||
for asset in assets:
|
||||
if asset.hostname in summary.get('dark', {}):
|
||||
if asset.hostname in results_summary.get('dark', {}):
|
||||
asset.connectivity = asset.UNREACHABLE
|
||||
elif asset.hostname in summary.get('contacted', []):
|
||||
elif asset.hostname in results_summary.get('contacted', []):
|
||||
asset.connectivity = asset.REACHABLE
|
||||
else:
|
||||
asset.connectivity = asset.UNKNOWN
|
||||
return summary
|
||||
|
||||
return results_summary
|
||||
|
||||
|
||||
@shared_task
|
||||
|
@ -243,8 +283,7 @@ def test_admin_user_connectivity_manual(admin_user):
|
|||
## System user connective ##
|
||||
|
||||
@shared_task
|
||||
def set_system_user_connectivity_info(system_user, result):
|
||||
summary = result[1]
|
||||
def set_system_user_connectivity_info(system_user, summary):
|
||||
system_user.connectivity = summary
|
||||
|
||||
|
||||
|
@ -258,18 +297,50 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
|||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS,
|
||||
run_as=system_user.username, created_by=system_user.org_id,
|
||||
|
||||
hosts = clean_hosts_by_protocol(system_user, hosts)
|
||||
if not hosts:
|
||||
return {}
|
||||
|
||||
hosts_category = {
|
||||
'linux': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
},
|
||||
'windows': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_WINDOWS_SYSTEM_USER_CONN_TASKS
|
||||
}
|
||||
}
|
||||
for host in hosts:
|
||||
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
|
||||
else hosts_category['linux']['hosts']
|
||||
hosts_list.append(host)
|
||||
|
||||
results_summary = dict(
|
||||
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
|
||||
)
|
||||
result = task.run()
|
||||
set_system_user_connectivity_info(system_user, result)
|
||||
return result
|
||||
for _, value in hosts_category.items():
|
||||
if not value['hosts']:
|
||||
continue
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
|
||||
pattern='all', options=const.TASK_OPTIONS,
|
||||
run_as=system_user.username,
|
||||
created_by=system_user.org_id,
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
results_summary['success'] &= summary['success']
|
||||
results_summary['contacted'].update(summary['contacted'])
|
||||
results_summary['dark'].update(summary['dark'])
|
||||
|
||||
set_system_user_connectivity_info(system_user, results_summary)
|
||||
return results_summary
|
||||
|
||||
|
||||
@shared_task
|
||||
|
@ -301,11 +372,7 @@ def test_system_user_connectivity_period():
|
|||
|
||||
#### Push system user tasks ####
|
||||
|
||||
def get_push_system_user_tasks(system_user):
|
||||
# Set root as system user is dangerous
|
||||
if system_user.username == "root":
|
||||
return []
|
||||
|
||||
def get_push_linux_system_user_tasks(system_user):
|
||||
tasks = []
|
||||
if system_user.password:
|
||||
tasks.append({
|
||||
|
@ -320,12 +387,12 @@ def get_push_system_user_tasks(system_user):
|
|||
})
|
||||
tasks.extend([
|
||||
{
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(system_user.username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(system_user.username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
},
|
||||
{
|
||||
'name': "Set home dir permission",
|
||||
|
@ -364,6 +431,46 @@ def get_push_system_user_tasks(system_user):
|
|||
)
|
||||
}
|
||||
})
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
def get_push_windows_system_user_tasks(system_user):
|
||||
tasks = []
|
||||
if system_user.password:
|
||||
tasks.append({
|
||||
'name': 'Add user {}'.format(system_user.username),
|
||||
'action': {
|
||||
'module': 'win_user',
|
||||
'args': 'fullname={} '
|
||||
'name={} '
|
||||
'password={} '
|
||||
'state=present '
|
||||
'update_password=always'
|
||||
'password_expired=no '
|
||||
'password_never_expires=yes '
|
||||
'groups="Users,Remote Desktop Users" '
|
||||
'groups_action=add '
|
||||
''.format(system_user.name,
|
||||
system_user.username,
|
||||
system_user.password),
|
||||
}
|
||||
})
|
||||
return tasks
|
||||
|
||||
|
||||
def get_push_system_user_tasks(host, system_user):
|
||||
if host.is_unixlike():
|
||||
tasks = get_push_linux_system_user_tasks(system_user)
|
||||
elif host.is_windows():
|
||||
tasks = get_push_windows_system_user_tasks(system_user)
|
||||
else:
|
||||
msg = _(
|
||||
"The asset {} system platform {} does not "
|
||||
"support run Ansible tasks".format(host.hostname, host.platform)
|
||||
)
|
||||
logger.info(msg)
|
||||
tasks = []
|
||||
return tasks
|
||||
|
||||
|
||||
|
@ -372,16 +479,29 @@ def push_system_user_util(system_user, assets, task_name):
|
|||
from ops.utils import update_or_create_ansible_task
|
||||
if not system_user.is_need_push():
|
||||
msg = _("Push system user task skip, auto push not enable or "
|
||||
"protocol is not ssh: {}").format(system_user.name)
|
||||
"protocol is not ssh or rdp: {}").format(system_user.name)
|
||||
logger.info(msg)
|
||||
return
|
||||
return {}
|
||||
|
||||
# Set root as system user is dangerous
|
||||
if system_user.username.lower() in ["root", "administrator"]:
|
||||
msg = _("For security, do not push user {}".format(system_user.username))
|
||||
logger.info(msg)
|
||||
return {}
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
|
||||
hosts = clean_hosts_by_protocol(system_user, hosts)
|
||||
if not hosts:
|
||||
return {}
|
||||
|
||||
for host in hosts:
|
||||
system_user.load_specific_asset_auth(host)
|
||||
tasks = get_push_system_user_tasks(system_user)
|
||||
tasks = get_push_system_user_tasks(host, system_user)
|
||||
if not tasks:
|
||||
continue
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
|
@ -423,6 +543,23 @@ def test_admin_user_connectability_period():
|
|||
pass
|
||||
|
||||
|
||||
#### Test Asset user connectivity task ####
|
||||
|
||||
def get_test_asset_user_connectivity_tasks(asset):
|
||||
if asset.is_unixlike():
|
||||
tasks = const.TEST_ASSET_USER_CONN_TASKS
|
||||
elif asset.is_windows():
|
||||
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
|
||||
else:
|
||||
msg = _(
|
||||
"The asset {} system platform {} does not "
|
||||
"support run Ansible tasks".format(asset.hostname, asset.platform)
|
||||
)
|
||||
logger.info(msg)
|
||||
tasks = []
|
||||
return tasks
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_asset_user_connectivity_info(asset_user, result):
|
||||
summary = result[1]
|
||||
|
@ -437,10 +574,14 @@ def test_asset_user_connectivity_util(asset_user, task_name):
|
|||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
tasks = const.TEST_ASSET_USER_CONN_TASKS
|
||||
|
||||
if not check_asset_can_run_ansible(asset_user.asset):
|
||||
return
|
||||
|
||||
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
|
||||
if not tasks:
|
||||
return
|
||||
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS,
|
||||
|
|
|
@ -95,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();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
{% if asset.is_support_ansible %}
|
||||
<tr class="no-borders-tr">
|
||||
<td>{% trans 'Test connective' %}:</td>
|
||||
<td>
|
||||
|
@ -118,7 +118,7 @@ function initAssetUserTable() {
|
|||
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-username="DEFAULT_USERNAME">{% trans "View auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
btn += view_btn;
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
{% if asset.is_support_ansible %}
|
||||
btn += test_btn;
|
||||
{% endif %}
|
||||
$(td).html(btn);
|
||||
|
|
|
@ -16,12 +16,24 @@
|
|||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
||||
<h3>{% trans 'Protocols' %}</h3>
|
||||
<div class="protocols">
|
||||
{% for fm in formset.forms %}
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 col-md-offset-2" style="text-align: right">{{ fm.name }}</div>
|
||||
<div class="col-md-6">{{ fm.port }}</div>
|
||||
<div class="col-md-1" style="padding: 6px 0">
|
||||
<a class="btn btn-danger btn-xs btn-protocol btn-del"><span class="fa fa-minus"></span> </a>
|
||||
<a class="btn btn-primary btn-xs btn-protocol btn-add" style="display: none"><span class="fa fa-plus"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
@ -55,6 +67,8 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% block extra %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
|
@ -73,11 +87,25 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var instanceId = "{{ object.id }}";
|
||||
var protocolLen = 0;
|
||||
function format(item) {
|
||||
var group = item.element.parentElement.label;
|
||||
return group + ':' + item.text;
|
||||
}
|
||||
|
||||
function protocolBtnShow() {
|
||||
$(".btn-protocol.btn-add").hide();
|
||||
$(".btn-protocol.btn-add:last").show();
|
||||
var btnDel = $(".btn-protocol.btn-del");
|
||||
if (btnDel.length === 1) {
|
||||
btnDel.addClass("disabled")
|
||||
} else {
|
||||
btnDel.removeClass("disabled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
|
@ -89,20 +117,133 @@ $(document).ready(function () {
|
|||
$('#id_nodes.select2').select2({
|
||||
closeOnSelect: false
|
||||
});
|
||||
$("#id_protocol").change(function (){
|
||||
var protocol = $("#id_protocol option:selected").text();
|
||||
var port = 22;
|
||||
if(protocol === 'rdp'){
|
||||
port = 3389;
|
||||
}
|
||||
else if(protocol === 'telnet (beta)'){
|
||||
port = 23;
|
||||
}
|
||||
else if(protocol === 'vnc'){
|
||||
port = 5901;
|
||||
}
|
||||
$("#id_port").val(port);
|
||||
protocolBtnShow()
|
||||
})
|
||||
.on("change", "#id_platform", function () {
|
||||
if (instanceId !== "") {
|
||||
return
|
||||
}
|
||||
var platform = $(this).val();
|
||||
var protocolRef = $(".protocols").find("select").first()
|
||||
var protocol = protocolRef.val();
|
||||
|
||||
var protocolShould = "";
|
||||
if (platform.startsWith("Windows")){
|
||||
protocolShould = "rdp"
|
||||
} else {
|
||||
protocolShould = "ssh"
|
||||
}
|
||||
if (protocol !== protocolShould) {
|
||||
protocolRef.val(protocolShould);
|
||||
protocolRef.trigger("change")
|
||||
}
|
||||
|
||||
})
|
||||
.on("click", ".btn-protocol.btn-del", function () {
|
||||
$(this).parent().parent().remove();
|
||||
protocolBtnShow()
|
||||
})
|
||||
.on("click", ".btn-protocol.btn-add", function () {
|
||||
var protocol = "";
|
||||
var protocolsRef = $(".protocols");
|
||||
var firstProtocolForm = protocolsRef.children().first();
|
||||
var newProtocolForm = firstProtocolForm.clone();
|
||||
var protocolChoices = $.map($(firstProtocolForm.find('select option')), function (option) {
|
||||
return option.value
|
||||
});
|
||||
var protocolsSet = $.map(protocolsRef.find('select option:selected'), function(option) {
|
||||
return option.value
|
||||
});
|
||||
for (var i=0;i<protocolChoices.length;i++) {
|
||||
var p = protocolChoices[i];
|
||||
if (protocolsSet.indexOf(p) === -1) {
|
||||
protocol = p;
|
||||
break
|
||||
}
|
||||
}
|
||||
if (protocol === "") {
|
||||
return
|
||||
}
|
||||
if (protocolLen === 0) {
|
||||
protocolLen = protocolsRef.length;
|
||||
}
|
||||
var selectName = "form-" + protocolLen + "-name";
|
||||
var selectId = "id_" + selectName;
|
||||
var portName = "form-" + protocolLen + "-port";
|
||||
var portId = "id_" + portName;
|
||||
newProtocolForm.find("select").prop("name", selectName).prop("id", selectId);
|
||||
newProtocolForm.find("input").prop("name", portName).prop("id", portId);
|
||||
newProtocolForm.find("option[value='" + protocol + "']").attr("selected", true);
|
||||
protocolsRef.append(newProtocolForm);
|
||||
protocolLen += 1;
|
||||
$("#" + selectId).trigger("change");
|
||||
protocolBtnShow()
|
||||
})
|
||||
.on("change", ".protocol-name", function () {
|
||||
var name = $(this).val();
|
||||
var port = 22;
|
||||
switch (name) {
|
||||
case "ssh":
|
||||
port = 22;
|
||||
break;
|
||||
case "rdp":
|
||||
port = 3389;
|
||||
break;
|
||||
case "telnet":
|
||||
port = 21;
|
||||
break;
|
||||
case "vnc":
|
||||
port = 5901;
|
||||
break;
|
||||
default:
|
||||
port = 22;
|
||||
break
|
||||
}
|
||||
$(this).parent().parent().find(".protocol-port").val(port);
|
||||
})
|
||||
</script>
|
||||
{% block form_submit %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on("submit", "form", function (evt) {
|
||||
evt.preventDefault();
|
||||
var the_url = '{% url 'api-assets:asset-list' %}';
|
||||
var redirect_to = '{% url "assets:asset-list" %}';
|
||||
var form = $("form");
|
||||
var protocols = {};
|
||||
var data = form.serializeObject();
|
||||
$.each(data, function (k, v) {
|
||||
if (k.startsWith("form")){
|
||||
delete data[k];
|
||||
var _k = k.split("-");
|
||||
var formName = _k.slice(0, 2).join("-");
|
||||
var key = _k[_k.length-1];
|
||||
if (!protocols[formName]) {
|
||||
protocols[formName] = {}
|
||||
}
|
||||
protocols[formName][key] = v
|
||||
}
|
||||
});
|
||||
protocols = $.map(protocols, function ( v) {
|
||||
return v
|
||||
});
|
||||
data["protocols"] = protocols;
|
||||
if (typeof data.labels === "string") {
|
||||
data["labels"] = [data["labels"]];
|
||||
}
|
||||
if (typeof data["nodes"] == "string") {
|
||||
data["nodes"] = [data["nodes"]]
|
||||
}
|
||||
var props = {
|
||||
url: the_url,
|
||||
data: data,
|
||||
method: "POST",
|
||||
form: form,
|
||||
redirect_to: redirect_to
|
||||
};
|
||||
formSubmit(props);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -69,12 +69,13 @@
|
|||
<td><b>{{ asset.public_ip|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Port' %}:</td>
|
||||
<td><b>{{ asset.port }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b>{{ asset.protocol }}</b></td>
|
||||
<td>{% trans 'Protocol' %}</td>
|
||||
<td>
|
||||
{% for protocol in asset.protocols.all %}
|
||||
<b>{{ protocol.name }}:</b>
|
||||
{{ protocol.port }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
|
@ -94,7 +95,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'CPU' %}:</td>
|
||||
<td><b>{{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }}</b></td>
|
||||
<td><b>{{ asset.cpu_info }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Memory' %}:</td>
|
||||
|
@ -166,7 +167,7 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
{% if asset.is_support_ansible %}
|
||||
<tr>
|
||||
<td>{% trans 'Refresh hardware' %}:</td>
|
||||
<td>
|
||||
|
|
|
@ -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 %}
|
||||
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/inputTags.jquery.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Node' %}</h3>
|
||||
{% bootstrap_field form.nodes layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Labels' %}</h3>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
|
||||
<div class="col-md-9">
|
||||
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||
<optgroup label="{{ name }}">
|
||||
{% for label in labels %}
|
||||
{% if label in form.labels.initial %}
|
||||
<option value="{{ label.id }}" selected>{{ label.value }}</option>
|
||||
{% else %}
|
||||
<option value="{{ label.id }}">{{ label.value }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block extra %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Configuration' %}</h3>
|
||||
{% bootstrap_field form.number layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function format(item) {
|
||||
var group = item.element.parentElement.label;
|
||||
return group + ':' + item.text;
|
||||
}
|
||||
{% block form_submit %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
$(".labels").select2({
|
||||
allowClear: true,
|
||||
templateSelection: format
|
||||
});
|
||||
})
|
||||
</script>
|
||||
.on("submit", "form", function (evt) {
|
||||
evt.preventDefault();
|
||||
var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}';
|
||||
var redirect_to = '{% url "assets:asset-list" %}';
|
||||
var form = $("form");
|
||||
var protocols = {};
|
||||
var data = form.serializeObject();
|
||||
$.each(data, function (k, v) {
|
||||
if (k.startsWith("form")){
|
||||
delete data[k];
|
||||
var _k = k.split("-");
|
||||
var formName = _k.slice(0, 2).join("-");
|
||||
var key = _k[_k.length-1];
|
||||
if (!protocols[formName]) {
|
||||
protocols[formName] = {}
|
||||
}
|
||||
protocols[formName][key] = v
|
||||
}
|
||||
});
|
||||
protocols = $.map(protocols, function ( v) {
|
||||
return v
|
||||
});
|
||||
data["protocols"] = protocols;
|
||||
if (typeof data["nodes"] == "string") {
|
||||
data["nodes"] = [data["nodes"]]
|
||||
}
|
||||
var props = {
|
||||
url: the_url,
|
||||
data: data,
|
||||
method: "PUT",
|
||||
form: form,
|
||||
redirect_to: redirect_to
|
||||
};
|
||||
formSubmit(props);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -118,7 +118,7 @@
|
|||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="only-ssh">
|
||||
<tr class="only-ssh-rdp">
|
||||
<td width="50%">{% trans 'Auto push' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
|
@ -135,7 +135,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% if system_user.auto_push %}
|
||||
<tr class="only-ssh">
|
||||
<tr class="only-ssh-rdp">
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
|
@ -144,7 +144,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="only-ssh">
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
|
@ -219,8 +219,12 @@ function updateCommandFilters(command_filters) {
|
|||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
if($('#id_protocol_type').text() === 'rdp'){
|
||||
$('.only-ssh').addClass('hidden')
|
||||
var protocol = $('#id_protocol_type').text();
|
||||
if(protocol !== 'ssh'){
|
||||
$('.only-ssh').addClass("hidden")
|
||||
}
|
||||
if(["ssh", "rdp"].indexOf(protocol) === -1){
|
||||
$('.only-ssh-rdp').addClass('hidden');
|
||||
}
|
||||
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
|
||||
$('.select2').select2()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{# 目前还不支持Windows的自动推送#}
|
||||
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
|
||||
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
|
||||
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch, Windows) does not support ansible, please manually fill in the account password. Automatic push for Windows is not currently supported.' %}
|
||||
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ from django.utils import timezone
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.forms.formsets import formset_factory
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
|
@ -30,8 +31,6 @@ from common.permissions import AdminUserRequiredMixin
|
|||
from common.const import (
|
||||
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
|
||||
)
|
||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||
from orgs.utils import current_org
|
||||
from .. import forms
|
||||
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
||||
|
||||
|
@ -102,10 +101,30 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
form["nodes"].initial = node
|
||||
return form
|
||||
|
||||
def get_protocol_formset(self):
|
||||
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
|
||||
if self.request.method == "POST":
|
||||
formset = ProtocolFormset(self.request.POST)
|
||||
else:
|
||||
formset = ProtocolFormset()
|
||||
return formset
|
||||
|
||||
def form_valid(self, form):
|
||||
formset = self.get_protocol_formset()
|
||||
valid = formset.is_valid()
|
||||
if not valid:
|
||||
return self.form_invalid(form)
|
||||
protocols = formset.save()
|
||||
instance = super().form_valid(form)
|
||||
instance.protocols.set(protocols)
|
||||
return instance
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
formset = self.get_protocol_formset()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset'),
|
||||
'formset': formset,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -160,10 +179,21 @@ class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
|||
template_name = 'assets/asset_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
def get_protocol_formset(self):
|
||||
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
|
||||
if self.request.method == "POST":
|
||||
formset = ProtocolFormset(self.request.POST)
|
||||
else:
|
||||
initial_data = [{"name": p.name, "port": p.port} for p in self.object.protocols.all()]
|
||||
formset = ProtocolFormset(initial=initial_data)
|
||||
return formset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
formset = self.get_protocol_formset()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update asset'),
|
||||
'formset': formset,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -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
|
|
@ -5,6 +5,7 @@ from django.http import JsonResponse
|
|||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import messages
|
||||
from rest_framework.utils import html
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
@ -203,3 +204,26 @@ class DatetimeSearchMixin:
|
|||
def get(self, request, *args, **kwargs):
|
||||
self.get_date_range()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ApiMessageMixin:
|
||||
success_message = _("%(name)s was %(action)s successfully")
|
||||
_action_map = {"create": _("create"), "update": _("update")}
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
data = {k: v for k, v in cleaned_data.items()}
|
||||
action = getattr(self, "action", "create")
|
||||
data["action"] = self._action_map.get(action)
|
||||
message = self.success_message % data
|
||||
return message
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
resp = super().dispatch(request, *args, **kwargs)
|
||||
if request.method.lower() in ("get", "delete"):
|
||||
return resp
|
||||
if resp.status_code >= 400:
|
||||
return resp
|
||||
message = self.get_success_message(resp.data)
|
||||
if message:
|
||||
messages.success(request, message)
|
||||
return resp
|
||||
|
|
|
@ -3,5 +3,22 @@
|
|||
from django.core.validators import RegexValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework.validators import (
|
||||
UniqueTogetherValidator, ValidationError
|
||||
)
|
||||
|
||||
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
|
||||
|
||||
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
|
||||
|
||||
|
||||
class ProjectUniqueValidator(UniqueTogetherValidator):
|
||||
def __call__(self, attrs):
|
||||
try:
|
||||
super().__call__(attrs)
|
||||
except ValidationError as e:
|
||||
errors = {}
|
||||
for field in self.fields:
|
||||
if field == "org_id":
|
||||
continue
|
||||
errors[field] = _('This field must be unique.')
|
||||
raise ValidationError(errors)
|
||||
|
|
|
@ -287,7 +287,10 @@ class Config(dict):
|
|||
return v
|
||||
|
||||
try:
|
||||
v = tp(v)
|
||||
if tp in [list, dict]:
|
||||
v = json.loads(v)
|
||||
else:
|
||||
v = tp(v)
|
||||
except Exception:
|
||||
pass
|
||||
return v
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,7 @@ class JMSBaseInventory(BaseInventory):
|
|||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'port': asset.port,
|
||||
'port': asset.ssh_port,
|
||||
'vars': dict(),
|
||||
'groups': [],
|
||||
}
|
||||
|
@ -29,8 +29,15 @@ class JMSBaseInventory(BaseInventory):
|
|||
info["vars"].update(self.make_proxy_command(asset))
|
||||
if run_as_admin:
|
||||
info.update(asset.get_auth_info())
|
||||
if asset.is_unixlike():
|
||||
info["become"] = asset.admin_user.become_info
|
||||
for node in asset.nodes.all():
|
||||
info["groups"].append(node.value)
|
||||
if asset.is_windows():
|
||||
info["vars"].update({
|
||||
"ansible_connection": "ssh",
|
||||
"ansible_shell_type": "cmd",
|
||||
})
|
||||
for label in asset.labels.all():
|
||||
info["vars"].update({
|
||||
label.name: label.value
|
||||
|
@ -73,7 +80,7 @@ class JMSInventory(JMSBaseInventory):
|
|||
"""
|
||||
def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None):
|
||||
"""
|
||||
:param host_id_list: ["test1", ]
|
||||
:param assets: assets
|
||||
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
|
||||
:param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username)
|
||||
:param become_info: 是否become成某个用户去执行
|
||||
|
@ -86,17 +93,14 @@ class JMSInventory(JMSBaseInventory):
|
|||
host_list = []
|
||||
|
||||
for asset in assets:
|
||||
info = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
|
||||
host_list.append(info)
|
||||
|
||||
if run_as:
|
||||
for host in host_list:
|
||||
host = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
|
||||
if run_as:
|
||||
run_user_info = self.get_run_user_info(host)
|
||||
host.update(run_user_info)
|
||||
|
||||
if become_info:
|
||||
for host in host_list:
|
||||
if become_info and asset.is_unixlike():
|
||||
host.update(become_info)
|
||||
host_list.append(host)
|
||||
|
||||
super().__init__(host_list=host_list)
|
||||
|
||||
def get_run_user_info(self, host):
|
||||
|
@ -133,12 +137,10 @@ class JMSCustomInventory(JMSBaseInventory):
|
|||
host_list = []
|
||||
|
||||
for asset in assets:
|
||||
info = self.convert_to_ansible(asset)
|
||||
host_list.append(info)
|
||||
|
||||
for host in host_list:
|
||||
host = self.convert_to_ansible(asset)
|
||||
run_user_info = self.get_run_user_info()
|
||||
host.update(run_user_info)
|
||||
host_list.append(host)
|
||||
|
||||
super().__init__(host_list=host_list)
|
||||
|
||||
|
|
|
@ -220,6 +220,7 @@ class AdHoc(models.Model):
|
|||
time_start = time.time()
|
||||
try:
|
||||
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
history.date_start = date_start
|
||||
print(_("{} Start task: {}").format(date_start, self.task.name))
|
||||
raw, summary = self._run_only()
|
||||
date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
|
|
@ -134,8 +134,11 @@ function getSelectedAssetsNode() {
|
|||
var assetsNodeId = [];
|
||||
var assetsNode = [];
|
||||
nodes.forEach(function (node) {
|
||||
if (node.meta.type === 'asset' && !node.isHidden && node.meta.asset.protocol === 'ssh') {
|
||||
if (assetsNodeId.indexOf(node.id) === -1) {
|
||||
if (node.meta.type === 'asset' && !node.isHidden) {
|
||||
var protocols = $.map(node.meta.asset.protocols, function (v) {
|
||||
return v.name
|
||||
});
|
||||
if (assetsNodeId.indexOf(node.id) === -1 && protocols.indexOf("ssh") > -1) {
|
||||
assetsNodeId.push(node.id);
|
||||
assetsNode.push(node)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ def update_or_create_ansible_task(
|
|||
run_as_admin=False, run_as=None, become_info=None,
|
||||
):
|
||||
if not hosts or not tasks or not task_name:
|
||||
return
|
||||
return None, None
|
||||
set_to_root_org()
|
||||
defaults = {
|
||||
'name': task_name,
|
||||
|
|
|
@ -9,8 +9,10 @@ from django.forms import ModelForm
|
|||
from django.http.response import HttpResponseForbidden
|
||||
from django.core.exceptions import ValidationError
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from .utils import (
|
||||
current_org, set_current_org, set_to_root_org, get_current_org_id
|
||||
)
|
||||
|
@ -216,3 +218,14 @@ class OrgResourceSerializerMixin(serializers.Serializer):
|
|||
但是coco需要资产的org_id字段,所以修改为CharField类型
|
||||
"""
|
||||
org_id = serializers.CharField(default=get_current_org_id)
|
||||
|
||||
def get_validators(self):
|
||||
_validators = super().get_validators()
|
||||
validators = []
|
||||
|
||||
for v in _validators:
|
||||
if isinstance(v, UniqueTogetherValidator) \
|
||||
and "org_id" in v.fields:
|
||||
v = ProjectUniqueValidator(v.queryset, v.fields)
|
||||
validators.append(v)
|
||||
return validators
|
||||
|
|
|
@ -154,7 +154,7 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
|
|||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
||||
assets = util.get_assets()
|
||||
for k, v in assets.items():
|
||||
system_users_granted = [s for s in v if s.protocol == k.protocol]
|
||||
system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
|
||||
k.system_users_granted = system_users_granted
|
||||
queryset.append(k)
|
||||
return queryset
|
||||
|
@ -215,8 +215,7 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
|
|||
for node, _assets in nodes.items():
|
||||
assets = _assets.keys()
|
||||
for k, v in _assets.items():
|
||||
system_users_granted = [s for s in v if
|
||||
s.protocol == k.protocol]
|
||||
system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
|
||||
k.system_users_granted = system_users_granted
|
||||
node.assets_granted = assets
|
||||
queryset.append(node)
|
||||
|
@ -364,7 +363,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
|
|||
for asset, system_users in nodes_granted[node].items():
|
||||
fake_node = asset.as_node()
|
||||
fake_node.assets_amount = 0
|
||||
system_users = [s for s in system_users if s.protocol == asset.protocol]
|
||||
system_users = [s for s in system_users if asset.has_protocol(s.protocol)]
|
||||
fake_node.asset.system_users_granted = system_users
|
||||
fake_node.key = node.key + ':0'
|
||||
fake_nodes.append(fake_node)
|
||||
|
@ -389,7 +388,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
|
|||
fake_node = asset.as_node()
|
||||
fake_node.assets_amount = 0
|
||||
system_users = [s for s in system_users if
|
||||
s.protocol == asset.protocol]
|
||||
asset.has_protocol(s.protocol)]
|
||||
fake_node.asset.system_users_granted = system_users
|
||||
fake_node.key = node.key + ':0'
|
||||
matched_assets.append(fake_node)
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, SystemUser, Node, RemoteApp
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
from assets.serializers import (
|
||||
AssetGrantedSerializer, NodeSerializer
|
||||
)
|
||||
from applications.serializers import RemoteAppSerializer
|
||||
from applications.models import RemoteApp
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ class AssetPermissionUtil:
|
|||
for perm in permissions:
|
||||
actions = perm.actions.all()
|
||||
for asset in perm.assets.all().valid().prefetch_related('nodes'):
|
||||
system_users = perm.system_users.filter(protocol=asset.protocol)
|
||||
system_users = perm.system_users.filter(protocol__in=asset.protocols_name)
|
||||
system_users = self._structured_system_user(system_users, actions)
|
||||
assets[asset].update(system_users)
|
||||
return assets
|
||||
|
@ -255,7 +255,7 @@ class AssetPermissionUtil:
|
|||
_assets = node.get_all_assets().valid().prefetch_related('nodes')
|
||||
for asset in _assets:
|
||||
for system_user, attr_dict in system_users.items():
|
||||
if system_user.protocol != asset.protocol:
|
||||
if not asset.has_protocol(system_user.protocol):
|
||||
continue
|
||||
if system_user in assets[asset]:
|
||||
actions = assets[asset][system_user]['actions']
|
||||
|
@ -279,15 +279,12 @@ class AssetPermissionUtil:
|
|||
resource=resource
|
||||
)
|
||||
|
||||
@property
|
||||
def node_key(self):
|
||||
return self.get_cache_key('NODES_WITH_ASSETS')
|
||||
|
||||
@property
|
||||
def asset_key(self):
|
||||
return self.get_cache_key('ASSETS')
|
||||
|
||||
@property
|
||||
def system_key(self):
|
||||
return self.get_cache_key('SYSTEM_USER')
|
||||
|
||||
|
@ -457,7 +454,7 @@ def parse_node_to_tree_node(node):
|
|||
|
||||
|
||||
def parse_asset_to_tree_node(node, asset, system_users):
|
||||
system_users_protocol_matched = [s for s in system_users if s.protocol == asset.protocol]
|
||||
system_users_protocol_matched = [s for s in system_users if asset.has_protocol(s.protocol)]
|
||||
icon_skin = 'file'
|
||||
if asset.platform.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
|
@ -490,8 +487,8 @@ def parse_asset_to_tree_node(node, asset, system_users):
|
|||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'port': asset.port,
|
||||
'protocol': asset.protocol,
|
||||
'protocols': [{"name": p.name, "port": p.port}
|
||||
for p in asset.protocols.all()],
|
||||
'platform': asset.platform,
|
||||
'domain': None if not asset.domain else asset.domain.id,
|
||||
'is_active': asset.is_active,
|
||||
|
|
|
@ -11,9 +11,8 @@ from django.conf import settings
|
|||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from orgs.utils import current_org
|
||||
from users.models import UserGroup
|
||||
from assets.models import RemoteApp
|
||||
|
||||
from ..hands import RemoteApp, UserGroup
|
||||
from ..models import RemoteAppPermission
|
||||
from ..forms import RemoteAppPermissionCreateUpdateForm
|
||||
|
||||
|
|
|
@ -453,4 +453,16 @@ div.dataTables_wrapper div.dataTables_filter {
|
|||
|
||||
#tree-refresh .fa-refresh {
|
||||
font: normal normal normal 14px/1 FontAwesome !important;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single, .select2-selection__arrow {
|
||||
height: 34px !important;
|
||||
}
|
||||
|
||||
.select2-selection {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
span.select2-selection__placeholder {
|
||||
line-height: 34px !important;
|
||||
}
|
||||
|
|
|
@ -165,11 +165,13 @@ function formSubmit(props) {
|
|||
/*
|
||||
{
|
||||
"form": $("form"),
|
||||
"data": {},
|
||||
"url": "",
|
||||
"method": "POST",
|
||||
"redirect_to": "",
|
||||
"success": function(data, textStatue, jqXHR){},
|
||||
"error": function(jqXHR, textStatus, errorThrown) {}
|
||||
"error": function(jqXHR, textStatus, errorThrown) {},
|
||||
"message": "",
|
||||
}
|
||||
*/
|
||||
props = props || {};
|
||||
|
@ -183,6 +185,10 @@ function formSubmit(props) {
|
|||
dataType: props.data_type || "json"
|
||||
}).done(function (data, textState, jqXHR) {
|
||||
if (redirect_to) {
|
||||
if (props.message) {
|
||||
var messages="ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
|
||||
setCookie("messages", messages)
|
||||
}
|
||||
location.href = redirect_to;
|
||||
} else if (typeof props.success === 'function') {
|
||||
return props.success(data, textState, jqXHR);
|
||||
|
@ -230,7 +236,15 @@ function formSubmit(props) {
|
|||
var help_msg = v.join("<br/>") ;
|
||||
helpBlockRef.html(help_msg);
|
||||
} else {
|
||||
noneFieldErrorMsg += v + '<br/>';
|
||||
$.each(v, function (kk, vv) {
|
||||
if (typeof errors === "object") {
|
||||
$.each(vv, function (kkk, vvv) {
|
||||
noneFieldErrorMsg += " " + vvv + '<br/>';
|
||||
})
|
||||
} else{
|
||||
noneFieldErrorMsg += vv + '<br/>';
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
if (noneFieldErrorRef.length === 1 && noneFieldErrorMsg !== '') {
|
||||
|
|
Loading…
Reference in New Issue