Unverified Commit 09565375 authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Merge pull request #3260 from jumpserver/dev

Dev
parents 05818671 864bcb27
## Jumpserver 多云环境下更好用的堡垒机 # Jumpserver 多云环境下更好用的堡垒机
![Total visitor](https://visitor-count-badge.herokuapp.com/total.svg?repo_id=jumpserver) ![Total visitor](https://visitor-count-badge.herokuapp.com/total.svg?repo_id=jumpserver)
![Visitors in today](https://visitor-count-badge.herokuapp.com/today.svg?repo_id=jumpserver) ![Visitors in today](https://visitor-count-badge.herokuapp.com/today.svg?repo_id=jumpserver)
...@@ -7,18 +7,17 @@ ...@@ -7,18 +7,17 @@
[![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/) [![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](http://www.paramiko.org/) [![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](http://www.paramiko.org/)
---- Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 机制的运维安全审计系统。
Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 的运维安全审计系统。 Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。
Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。 Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
改变世界,从一点点开始。 改变世界,从一点点开始。
### 核心功能列表 注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 Jumpserver 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。
----
## 核心功能列表
<table class="subscription-level-table"> <table class="subscription-level-table">
<tr class="subscription-level-tr-border"> <tr class="subscription-level-tr-border">
...@@ -173,31 +172,27 @@ Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向 ...@@ -173,31 +172,27 @@ Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向
</tr> </tr>
</table> </table>
### 安装及使用文档 ## 安装及使用指南
----
- [Docker 快速安装文档](http://docs.jumpserver.org/zh/docs/dockerinstall.html) - [Docker 快速安装文档](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
- [Step by Step 安装文档](http://docs.jumpserver.org/zh/docs/step_by_step.html) - [Step by Step 安装文档](http://docs.jumpserver.org/zh/docs/step_by_step.html)
- [完整文档](http://docs.jumpserver.org) - [完整文档](http://docs.jumpserver.org)
### 演示视频和系统截图 ## 演示视频和截屏
----
我们提供了演示视频和系统截图可以让你快速了解 Jumpserver 我们提供了演示视频和系统截图可以让你快速了解 Jumpserver
- [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4) - [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4)
- [系统截图](http://docs.jumpserver.org/zh/docs/snapshot.html) - [系统截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK ## SDK
----
我们编写了一些SDK,供你的其它系统快速和 Jumpserver API 交互。 我们编写了一些SDK,供您的其它系统快速和 Jumpserver API 交互:
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver 其它组件使用这个 SDK 完成交互 - [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver 其它组件使用这个 SDK 完成交互
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的 Java 版本的 SDK - [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的 Java 版本的 SDK
### License & Copyright ## License & Copyright
----
Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved. Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved.
......
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
from rest_framework import generics from rest_framework import generics
from rest_framework.pagination import LimitOffsetPagination
from rest_framework_bulk import BulkModelViewSet
from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin, IsAppUser from ..hands import IsOrgAdmin, IsAppUser
from ..models import RemoteApp from ..models import RemoteApp
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
...@@ -16,13 +15,12 @@ __all__ = [ ...@@ -16,13 +15,12 @@ __all__ = [
] ]
class RemoteAppViewSet(BulkModelViewSet): class RemoteAppViewSet(OrgBulkModelViewSet):
filter_fields = ('name',) filter_fields = ('name',)
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
queryset = RemoteApp.objects.all() queryset = RemoteApp.objects.all()
serializer_class = RemoteAppSerializer serializer_class = RemoteAppSerializer
pagination_class = LimitOffsetPagination
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django import forms from django import forms
from orgs.mixins import OrgModelForm from orgs.mixins.forms import OrgModelForm
from assets.models import SystemUser from assets.models import SystemUser
from ..models import RemoteApp from ..models import RemoteApp
...@@ -89,23 +89,16 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm): ...@@ -89,23 +89,16 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
field_asset = self.fields['asset'] field_asset = self.fields['asset']
field_asset.queryset = field_asset.queryset.has_protocol('rdp') field_asset.queryset = field_asset.queryset.has_protocol('rdp')
field_system_user = self.fields['system_user']
field_system_user.queryset = field_system_user.queryset.filter(
protocol=SystemUser.PROTOCOL_RDP
)
class Meta: class Meta:
model = RemoteApp model = RemoteApp
fields = [ fields = [
'name', 'asset', 'system_user', 'type', 'path', 'comment' 'name', 'asset', 'type', 'path', 'comment'
] ]
widgets = { widgets = {
'asset': forms.Select(attrs={ 'asset': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Asset') 'class': 'select2', 'data-placeholder': _('Asset')
}), }),
'system_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('System user')
})
} }
def _clean_params(self): def _clean_params(self):
......
# Generated by Django 2.1.7 on 2019-09-09 09:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('applications', '0001_initial'),
('perms', '0009_remoteapppermission_system_users'),
]
operations = [
migrations.RemoveField(
model_name='remoteapp',
name='system_user',
),
]
...@@ -5,7 +5,7 @@ import uuid ...@@ -5,7 +5,7 @@ import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from common.fields.model import EncryptJsonDictTextField from common.fields.model import EncryptJsonDictTextField
from .. import const from .. import const
...@@ -22,10 +22,6 @@ class RemoteApp(OrgModelMixin): ...@@ -22,10 +22,6 @@ class RemoteApp(OrgModelMixin):
asset = models.ForeignKey( asset = models.ForeignKey(
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset') 'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
) )
system_user = models.ForeignKey(
'assets.SystemUser', on_delete=models.CASCADE,
verbose_name=_('System user')
)
type = models.CharField( type = models.CharField(
default=const.REMOTE_APP_TYPE_CHROME, default=const.REMOTE_APP_TYPE_CHROME,
choices=const.REMOTE_APP_TYPE_CHOICES, choices=const.REMOTE_APP_TYPE_CHOICES,
...@@ -80,10 +76,3 @@ class RemoteApp(OrgModelMixin): ...@@ -80,10 +76,3 @@ class RemoteApp(OrgModelMixin):
'id': self.asset.id, 'id': self.asset.id,
'hostname': self.asset.hostname 'hostname': self.asset.hostname
} }
@property
def system_user_info(self):
return {
'id': self.system_user.id,
'name': self.system_user.name
}
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .. import const from .. import const
from ..models import RemoteApp from ..models import RemoteApp
...@@ -73,13 +73,13 @@ class RemoteAppSerializer(BulkOrgResourceModelSerializer): ...@@ -73,13 +73,13 @@ class RemoteAppSerializer(BulkOrgResourceModelSerializer):
model = RemoteApp model = RemoteApp
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = [ fields = [
'id', 'name', 'asset', 'system_user', 'type', 'path', 'params', 'id', 'name', 'asset', 'type', 'path', 'params',
'comment', 'created_by', 'date_created', 'asset_info', 'comment', 'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display', 'get_type_display',
] ]
read_only_fields = [ read_only_fields = [
'created_by', 'date_created', 'asset_info', 'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display' 'get_type_display'
] ]
...@@ -89,7 +89,7 @@ class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer): ...@@ -89,7 +89,7 @@ class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = RemoteApp model = RemoteApp
fields = [ fields = [
'id', 'name', 'asset', 'system_user', 'parameter_remote_app', 'id', 'name', 'asset', 'parameter_remote_app',
] ]
read_only_fields = ['parameter_remote_app'] read_only_fields = ['parameter_remote_app']
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
{% csrf_token %} {% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %} {% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.asset layout="horizontal" %} {% bootstrap_field form.asset layout="horizontal" %}
{% bootstrap_field form.system_user layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %} {% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.path layout="horizontal" %} {% bootstrap_field form.path layout="horizontal" %}
......
...@@ -57,10 +57,6 @@ ...@@ -57,10 +57,6 @@
<td>{% trans 'Asset' %}:</td> <td>{% trans 'Asset' %}:</td>
<td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td> <td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td>
</tr> </tr>
<tr>
<td>{% trans 'System user' %}:</td>
<td><b><a href="{% url 'assets:system-user-detail' pk=remote_app.system_user.id %}">{{ remote_app.system_user.name }}</a></b></td>
</tr>
<tr> <tr>
<td>{% trans 'App type' %}:</td> <td>{% trans 'App type' %}:</td>
<td><b>{{ remote_app.get_type_display }}</b></td> <td><b>{{ remote_app.get_type_display }}</b></td>
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th> <th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -47,12 +46,11 @@ function initTable() { ...@@ -47,12 +46,11 @@ function initTable() {
var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>'; var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
}}, }},
{targets: 4, createdCell: function (td, cellData, rowData) { {targets: 3, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name); var comment = htmlEscape(cellData);
var detail_btn = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + name + '</a>'; $(td).html(comment)
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
}}, }},
{targets: 6, createdCell: function (td, cellData, rowData) { {targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "applications:remote-app-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData); var update_btn = '<a href="{% url "applications:remote-app-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
...@@ -64,7 +62,6 @@ function initTable() { ...@@ -64,7 +62,6 @@ function initTable() {
{data: "name" }, {data: "name" },
{data: "get_type_display", orderable: false}, {data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false}, {data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment"}, {data: "comment"},
{data: "id", orderable: false} {data: "id", orderable: false}
], ],
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th> <th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -49,12 +48,8 @@ function initTable() { ...@@ -49,12 +48,8 @@ function initTable() {
var hostname = htmlEscape(cellData.hostname); var hostname = htmlEscape(cellData.hostname);
$(td).html(hostname); $(td).html(hostname);
}}, }},
{targets: 4, createdCell: function (td, cellData, rowData) { {targets: 5, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name); var conn_btn = '<a href="{% url "luna-view" %}?type=remote_app&login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(name);
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(conn_btn) $(td).html(conn_btn)
}} }}
], ],
...@@ -64,7 +59,6 @@ function initTable() { ...@@ -64,7 +59,6 @@ function initTable() {
{data: "name"}, {data: "name"},
{data: "get_type_display", orderable: false}, {data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false}, {data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment", orderable: false}, {data: "comment", orderable: false},
{data: "id", orderable: false} {data: "id", orderable: false}
] ]
......
# coding:utf-8 # coding:utf-8
# #
from django.urls import path from django.urls import path, re_path
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from common import api as capi
from .. import api from .. import api
app_name = 'applications' app_name = 'applications'
router = BulkRouter() router = BulkRouter()
router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app') router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app')
urlpatterns = [ urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/', path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
api.RemoteAppConnectionInfoApi.as_view(), ]
name='remote-app-connection-info') old_version_urlpatterns = [
re_path('(?P<resource>remote-app)/.*', capi.redirect_plural_name_api)
] ]
urlpatterns += router.urls urlpatterns += router.urls + old_version_urlpatterns
...@@ -6,3 +6,4 @@ from .node import * ...@@ -6,3 +6,4 @@ from .node import *
from .domain import * from .domain import *
from .cmd_filter import * from .cmd_filter import *
from .asset_user import * from .asset_user import *
from .gathered_user import *
...@@ -17,10 +17,9 @@ from django.db import transaction ...@@ -17,10 +17,9 @@ from django.db import transaction
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from common.mixins import IDInCacheFilterMixin from common.mixins import CommonApiMixin
from common.utils import get_logger from common.utils import get_logger
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset from ..models import AdminUser, Asset
...@@ -36,7 +35,7 @@ __all__ = [ ...@@ -36,7 +35,7 @@ __all__ = [
] ]
class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): class AdminUserViewSet(OrgBulkModelViewSet):
""" """
Admin user api set, for add,delete,update,list,retrieve resource Admin user api set, for add,delete,update,list,retrieve resource
""" """
...@@ -46,11 +45,6 @@ class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): ...@@ -46,11 +45,6 @@ class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
queryset = AdminUser.objects.all() queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
pagination_class = LimitOffsetPagination
def get_queryset(self):
queryset = super().get_queryset().all()
return queryset
class AdminUserAuthApi(generics.UpdateAPIView): class AdminUserAuthApi(generics.UpdateAPIView):
...@@ -98,7 +92,6 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -98,7 +92,6 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
class AdminUserAssetsListView(generics.ListAPIView): class AdminUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer serializer_class = serializers.AssetSimpleSerializer
pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip") filter_fields = ("hostname", "ip")
http_method_names = ['get'] http_method_names = ['get']
search_fields = filter_fields search_fields = filter_fields
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
import random import random
from rest_framework import generics from rest_framework import generics
from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework.pagination import LimitOffsetPagination
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.core.cache import cache
from django.db.models import Q
from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from ..models import Asset, AdminUser, Node from ..models import Asset, AdminUser, Node
from .. import serializers from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \ from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectivity_manual test_asset_connectivity_manual
from ..utils import LabelFilter from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'AssetViewSet', 'AssetListUpdateApi', 'AssetViewSet',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi', 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi', 'AssetBulkUpdateSelectAPI' 'AssetGatewayApi',
] ]
class AssetViewSet(LabelFilter, OrgBulkModelViewSet): class AssetViewSet(OrgBulkModelViewSet):
""" """
API endpoint that allows Asset to be viewed or edited. API endpoint that allows Asset to be viewed or edited.
""" """
...@@ -46,9 +34,8 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet): ...@@ -46,9 +34,8 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
ordering_fields = ("hostname", "ip", "port", "cpu_cores") ordering_fields = ("hostname", "ip", "port", "cpu_cores")
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
success_message = _("%(hostname)s was %(action)s successfully") extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
def set_assets_node(self, assets): def set_assets_node(self, assets):
if not isinstance(assets, list): if not isinstance(assets, list):
...@@ -65,66 +52,6 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet): ...@@ -65,66 +52,6 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
assets = serializer.save() assets = serializer.save()
self.set_assets_node(assets) self.set_assets_node(assets)
def filter_node(self, queryset):
node_id = self.request.query_params.get("node_id")
if not node_id:
return queryset
node = get_object_or_404(Node, id=node_id)
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
if node.is_root() and show_current_asset:
queryset = queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
)
elif node.is_root() and not show_current_asset:
pass
elif not node.is_root() and show_current_asset:
queryset = queryset.filter(nodes=node)
else:
queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
)
return queryset.distinct()
def filter_admin_user_id(self, queryset):
admin_user_id = self.request.query_params.get('admin_user_id')
if not admin_user_id:
return queryset
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
queryset = queryset.filter(admin_user=admin_user)
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_node(queryset)
queryset = self.filter_admin_user_id(queryset)
return queryset
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""
Asset bulk update api
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdmin,)
class AssetBulkUpdateSelectAPI(APIView):
permission_classes = (IsOrgAdmin,)
def post(self, request, *args, **kwargs):
assets_id = request.data.get('assets_id', '')
if assets_id:
spm = uuid.uuid4().hex
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
cache.set(key, assets_id, 300)
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
return Response({'url': url})
error = _('Please select assets that need to be updated')
return Response({'error': error}, status=400)
class AssetRefreshHardwareApi(generics.RetrieveAPIView): class AssetRefreshHardwareApi(generics.RetrieveAPIView):
""" """
......
...@@ -2,15 +2,15 @@ ...@@ -2,15 +2,15 @@
# #
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import viewsets, status, generics from rest_framework import generics
from rest_framework.pagination import LimitOffsetPagination
from rest_framework import filters from rest_framework import filters
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.http import Http404
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger from common.utils import get_object_or_none, get_logger
from common.mixins import IDInCacheFilterMixin from common.mixins import CommonApiMixin
from ..backends import AssetUserManager from ..backends import AssetUserManager
from ..models import Asset, Node, SystemUser, AdminUser from ..models import Asset, Node, SystemUser, AdminUser
from .. import serializers from .. import serializers
...@@ -49,11 +49,10 @@ class AssetUserSearchBackend(filters.BaseFilterBackend): ...@@ -49,11 +49,10 @@ class AssetUserSearchBackend(filters.BaseFilterBackend):
if field in ("node_id", "system_user_id", "admin_user_id"): if field in ("node_id", "system_user_id", "admin_user_id"):
continue continue
_queryset |= queryset.filter(**{field: value}) _queryset |= queryset.filter(**{field: value})
return _queryset return _queryset.distinct()
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetUserSerializer serializer_class = serializers.AssetUserSerializer
permission_classes = [IsOrgAdminOrAppUser] permission_classes = [IsOrgAdminOrAppUser]
http_method_names = ['get', 'post'] http_method_names = ['get', 'post']
...@@ -67,6 +66,9 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): ...@@ -67,6 +66,9 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
AssetUserFilterBackend, AssetUserSearchBackend, AssetUserFilterBackend, AssetUserSearchBackend,
) )
def allow_bulk_destroy(self, qs, filtered):
return False
def get_queryset(self): def get_queryset(self):
# 尽可能先返回更少的数据 # 尽可能先返回更少的数据
username = self.request.GET.get('username') username = self.request.GET.get('username')
...@@ -115,14 +117,6 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView): ...@@ -115,14 +117,6 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
status_code = status.HTTP_200_OK
if not instance:
status_code = status.HTTP_400_BAD_REQUEST
return Response(serializer.data, status=status_code)
def get_object(self): def get_object(self):
query_params = self.request.query_params query_params = self.request.query_params
username = query_params.get('username') username = query_params.get('username')
...@@ -133,8 +127,7 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView): ...@@ -133,8 +127,7 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
manger = AssetUserManager() manger = AssetUserManager()
instance = manger.get(username, asset, prefer=prefer) instance = manger.get(username, asset, prefer=prefer)
except Exception as e: except Exception as e:
logger.error(e, exc_info=True) raise Http404("Not found")
return None
else: else:
return instance return instance
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import CommandFilter, CommandFilterRule from ..models import CommandFilter, CommandFilterRule
from .. import serializers from .. import serializers
...@@ -13,21 +12,19 @@ from .. import serializers ...@@ -13,21 +12,19 @@ from .. import serializers
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet'] __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
class CommandFilterViewSet(BulkModelViewSet): class CommandFilterViewSet(OrgBulkModelViewSet):
filter_fields = ("name",) filter_fields = ("name",)
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
queryset = CommandFilter.objects.all() queryset = CommandFilter.objects.all()
serializer_class = serializers.CommandFilterSerializer serializer_class = serializers.CommandFilterSerializer
pagination_class = LimitOffsetPagination
class CommandFilterRuleViewSet(BulkModelViewSet): class CommandFilterRuleViewSet(OrgBulkModelViewSet):
filter_fields = ("content",) filter_fields = ("content",)
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer serializer_class = serializers.CommandFilterRuleSerializer
pagination_class = LimitOffsetPagination
def get_queryset(self): def get_queryset(self):
fpk = self.kwargs.get('filter_pk') fpk = self.kwargs.get('filter_pk')
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from rest_framework_bulk import BulkModelViewSet
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework.pagination import LimitOffsetPagination
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet
from ..models import Domain, Gateway from ..models import Domain, Gateway
from .. import serializers from .. import serializers
...@@ -16,34 +14,23 @@ logger = get_logger(__file__) ...@@ -16,34 +14,23 @@ logger = get_logger(__file__)
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"] __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(BulkModelViewSet): class DomainViewSet(OrgBulkModelViewSet):
queryset = Domain.objects.all() queryset = Domain.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DomainSerializer serializer_class = serializers.DomainSerializer
pagination_class = LimitOffsetPagination
def get_queryset(self):
queryset = super().get_queryset().all()
return queryset
def get_serializer_class(self): def get_serializer_class(self):
if self.request.query_params.get('gateway'): if self.request.query_params.get('gateway'):
return serializers.DomainWithGatewaySerializer return serializers.DomainWithGatewaySerializer
return super().get_serializer_class() return super().get_serializer_class()
def get_permissions(self):
if self.request.query_params.get('gateway'):
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
class GatewayViewSet(BulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet):
filter_fields = ("domain__name", "name", "username", "ip", "domain") filter_fields = ("domain__name", "name", "username", "ip", "domain")
search_fields = filter_fields search_fields = filter_fields
queryset = Gateway.objects.all() queryset = Gateway.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer serializer_class = serializers.GatewaySerializer
pagination_class = LimitOffsetPagination
class GatewayTestConnectionApi(SingleObjectMixin, APIView): class GatewayTestConnectionApi(SingleObjectMixin, APIView):
......
# -*- coding: utf-8 -*-
#
from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser
from common.permissions import IsOrgAdmin
from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend
__all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
queryset = GatheredUser.objects.all()
serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present']
search_fields = ['username', 'asset__ip', 'asset__hostname']
...@@ -13,11 +13,10 @@ ...@@ -13,11 +13,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from rest_framework.pagination import LimitOffsetPagination
from django.db.models import Count from django.db.models import Count
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import Label from ..models import Label
from .. import serializers from .. import serializers
...@@ -32,7 +31,6 @@ class LabelViewSet(OrgBulkModelViewSet): ...@@ -32,7 +31,6 @@ class LabelViewSet(OrgBulkModelViewSet):
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer serializer_class = serializers.LabelSerializer
pagination_class = LimitOffsetPagination
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
if request.query_params.get("distinct"): if request.query_params.get("distinct"):
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from rest_framework import generics, mixins, viewsets from rest_framework import generics
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
...@@ -22,11 +22,11 @@ from django.shortcuts import get_object_or_404 ...@@ -22,11 +22,11 @@ from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.tree import TreeNodeSerializer from common.tree import TreeNodeSerializer
from orgs.mixins.api import OrgModelViewSet
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import Node from ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
from .. import serializers from .. import serializers
from ..utils import NodeUtil
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -35,33 +35,29 @@ __all__ = [ ...@@ -35,33 +35,29 @@ __all__ = [
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi', 'NodeListAsTreeApi', 'TestNodeConnectiveApi', 'NodeListAsTreeApi',
'NodeChildrenAsTreeApi', 'RefreshAssetsAmount', 'NodeChildrenAsTreeApi', 'RefreshNodesCacheApi',
] ]
class NodeViewSet(viewsets.ModelViewSet): class NodeViewSet(OrgModelViewSet):
filter_fields = ('value', 'key', ) filter_fields = ('value', 'key', 'id')
search_fields = filter_fields search_fields = ('value', )
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
# 仅支持根节点指直接创建,子节点下的节点需要通过children接口创建
def perform_create(self, serializer): def perform_create(self, serializer):
child_key = Node.root().get_next_child_key() child_key = Node.org_root().get_next_child_key()
serializer.validated_data["key"] = child_key serializer.validated_data["key"] = child_key
serializer.save() serializer.save()
def update(self, request, *args, **kwargs): def perform_update(self, serializer):
node = self.get_object() node = self.get_object()
if node.is_root(): if node.is_org_root() and node.value != serializer.validated_data['value']:
node_value = node.value msg = _("You can't update the root node name")
post_value = request.data.get('value') raise ValidationError({"error": msg})
if node_value != post_value: return super().perform_update(serializer)
return Response(
{"msg": _("You can't update the root node name")},
status=400
)
return super().update(request, *args, **kwargs)
class NodeListAsTreeApi(generics.ListAPIView): class NodeListAsTreeApi(generics.ListAPIView):
...@@ -79,21 +75,74 @@ class NodeListAsTreeApi(generics.ListAPIView): ...@@ -79,21 +75,74 @@ class NodeListAsTreeApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
@staticmethod
def to_tree_queryset(queryset):
queryset = [node.as_tree_node() for node in queryset]
return queryset
def get_queryset(self): def get_queryset(self):
queryset = Node.objects.all() queryset = Node.objects.all()
util = NodeUtil()
nodes = util.get_nodes_by_queryset(queryset)
queryset = [node.as_tree_node() for node in nodes]
return queryset return queryset
@staticmethod def filter_queryset(self, queryset):
def refresh_nodes(queryset): queryset = super().filter_queryset(queryset)
Node.expire_nodes_assets_amount() queryset = self.to_tree_queryset(queryset)
Node.expire_nodes_full_value() return queryset
class NodeChildrenApi(generics.ListCreateAPIView):
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
is_initial = False
def initial(self, request, *args, **kwargs):
self.instance = self.get_object()
return super().initial(request, *args, **kwargs)
def perform_create(self, serializer):
data = serializer.validated_data
_id = data.get("id")
value = data.get("value")
if not value:
value = self.instance.get_next_child_preset_name()
node = self.instance.create_child(value=value, _id=_id)
# 避免查询 full value
node._full_value = node.value
serializer.instance = node
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
key = self.request.query_params.get("key")
if not pk and not key:
node = Node.org_root()
self.is_initial = True
return node
if pk:
node = get_object_or_404(Node, pk=pk)
else:
node = get_object_or_404(Node, key=key)
return node
def get_queryset(self):
query_all = self.request.query_params.get("all", "0") == "all"
if not self.instance:
return Node.objects.none()
if self.is_initial:
with_self = True
else:
with_self = False
if query_all:
queryset = self.instance.get_all_children(with_self=with_self)
else:
queryset = self.instance.get_children(with_self=with_self)
return queryset return queryset
class NodeChildrenAsTreeApi(generics.ListAPIView): class NodeChildrenAsTreeApi(NodeChildrenApi):
""" """
节点子节点作为树返回, 节点子节点作为树返回,
[ [
...@@ -106,39 +155,26 @@ class NodeChildrenAsTreeApi(generics.ListAPIView): ...@@ -106,39 +155,26 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
] ]
""" """
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
node = None http_method_names = ['get']
is_root = False
def get_queryset(self): def get_queryset(self):
self.check_need_refresh_nodes() queryset = super().get_queryset()
node_key = self.request.query_params.get('key')
util = NodeUtil()
# 是否包含自己
with_self = False
if not node_key:
node_key = Node.root().key
with_self = True
self.node = util.get_node_by_key(node_key)
queryset = self.node.get_children(with_self=with_self)
queryset = [node.as_tree_node() for node in queryset] queryset = [node.as_tree_node() for node in queryset]
queryset = self.add_assets_if_need(queryset)
queryset = sorted(queryset) queryset = sorted(queryset)
return queryset return queryset
def filter_assets(self, queryset): def add_assets_if_need(self, queryset):
include_assets = self.request.query_params.get('assets', '0') == '1' include_assets = self.request.query_params.get('assets', '0') == '1'
if not include_assets: if not include_assets:
return queryset return queryset
assets = self.node.get_assets().only( assets = self.instance.get_assets().only(
"id", "hostname", "ip", 'platform', "os", "org_id", "protocols", "id", "hostname", "ip", 'platform', "os",
"org_id", "protocols",
) )
for asset in assets: for asset in assets:
queryset.append(asset.as_tree_node(self.node)) queryset.append(asset.as_tree_node(self.instance))
return queryset
def filter_queryset(self, queryset):
queryset = self.filter_assets(queryset)
return queryset return queryset
def check_need_refresh_nodes(self): def check_need_refresh_nodes(self):
...@@ -146,59 +182,6 @@ class NodeChildrenAsTreeApi(generics.ListAPIView): ...@@ -146,59 +182,6 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
Node.refresh_nodes() Node.refresh_nodes()
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
instance = self.get_object()
if not request.data.get("value"):
request.data["value"] = instance.get_next_child_preset_name()
return super().post(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
instance = self.get_object()
value = request.data.get("value")
_id = request.data.get('id') or None
values = [child.value for child in instance.get_children()]
if value in values:
raise ValidationError(
'The same level node name cannot be the same'
)
node = instance.create_child(value=value, _id=_id)
return Response(self.serializer_class(instance=node).data, status=201)
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if not pk:
node = Node.root()
else:
node = get_object_or_404(Node, pk=pk)
return node
def get_queryset(self):
queryset = []
query_all = self.request.query_params.get("all")
node = self.get_object()
if node is None:
node = Node.root()
node.assets__count = node.get_all_assets().count()
queryset.append(node)
if query_all:
children = node.get_all_children()
else:
children = node.get_children()
queryset.extend(list(children))
return queryset
class NodeAssetsApi(generics.ListAPIView): class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
...@@ -251,7 +234,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView): ...@@ -251,7 +234,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
def perform_update(self, serializer): def perform_update(self, serializer):
assets = serializer.validated_data.get('assets') assets = serializer.validated_data.get('assets')
instance = self.get_object() instance = self.get_object()
if instance != Node.root(): if instance != Node.org_root():
instance.assets.remove(*tuple(assets)) instance.assets.remove(*tuple(assets))
else: else:
assets = [asset for asset in assets if asset.nodes.count() > 1] assets = [asset for asset in assets if asset.nodes.count() > 1]
...@@ -299,10 +282,13 @@ class TestNodeConnectiveApi(APIView): ...@@ -299,10 +282,13 @@ class TestNodeConnectiveApi(APIView):
return Response({"task": task.id}) return Response({"task": task.id})
class RefreshAssetsAmount(APIView): class RefreshNodesCacheApi(APIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
model = Node
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.model.expire_nodes_assets_amount() Node.refresh_nodes()
return Response("Ok") return Response("Ok")
def delete(self, *args, **kwargs):
self.get(*args, **kwargs)
return Response(status=204)
...@@ -16,18 +16,17 @@ ...@@ -16,18 +16,17 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from common.serializers import CeleryTaskSerializer
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.mixins import IDInCacheFilterMixin from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import OrgBulkModelViewSet
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
from .. import serializers from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \ from ..tasks import (
test_system_user_connectivity_manual, push_system_user_a_asset_manual, \ push_system_user_to_assets_manual, test_system_user_connectivity_manual,
test_system_user_connectivity_a_asset push_system_user_a_asset_manual, test_system_user_connectivity_a_asset,
)
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -49,7 +48,6 @@ class SystemUserViewSet(OrgBulkModelViewSet): ...@@ -49,7 +48,6 @@ class SystemUserViewSet(OrgBulkModelViewSet):
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer serializer_class = serializers.SystemUserSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
pagination_class = LimitOffsetPagination
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset().all() queryset = super().get_queryset().all()
...@@ -92,6 +90,7 @@ class SystemUserPushApi(generics.RetrieveAPIView): ...@@ -92,6 +90,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
""" """
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
system_user = self.get_object() system_user = self.get_object()
...@@ -108,6 +107,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -108,6 +107,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
""" """
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
system_user = self.get_object() system_user = self.get_object()
...@@ -118,7 +118,6 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -118,7 +118,6 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
class SystemUserAssetsListView(generics.ListAPIView): class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer serializer_class = serializers.AssetSimpleSerializer
pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip") filter_fields = ("hostname", "ip")
http_method_names = ['get'] http_method_names = ['get']
search_fields = filter_fields search_fields = filter_fields
......
from __future__ import unicode_literals from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig
from django.db.models.signals import post_migrate
def initial_some_nodes():
from .models import Node
Node.initial_some_nodes()
def initial_some_nodes_callback(sender, **kwargs):
initial_some_nodes()
class AssetsConfig(AppConfig): class AssetsConfig(AppConfig):
name = 'assets' name = 'assets'
def ready(self): def ready(self):
from . import signals_handler
super().ready() super().ready()
from . import signals_handler
try:
initial_some_nodes()
except Exception:
post_migrate.connect(initial_some_nodes_callback, sender=self)
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from collections import defaultdict
from .base import BaseBackend from .base import BaseBackend
...@@ -23,6 +24,7 @@ class AssetUserBackend(BaseBackend): ...@@ -23,6 +24,7 @@ class AssetUserBackend(BaseBackend):
queryset = queryset.filter(username=username) queryset = queryset.filter(username=username)
if assets: if assets:
queryset = queryset.filter(assets__in=assets).distinct() queryset = queryset.filter(assets__in=assets).distinct()
queryset = cls.filter_queryset_more(queryset) queryset = cls.filter_queryset_more(queryset)
instances = cls.construct_authbook_objects(queryset, assets) instances = cls.construct_authbook_objects(queryset, assets)
return instances return instances
...@@ -30,10 +32,26 @@ class AssetUserBackend(BaseBackend): ...@@ -30,10 +32,26 @@ class AssetUserBackend(BaseBackend):
@classmethod @classmethod
def construct_authbook_objects(cls, asset_users, assets): def construct_authbook_objects(cls, asset_users, assets):
instances = [] instances = []
assets_user_assets_map = defaultdict(set)
if isinstance(asset_users, list):
assets_user_assets_map = {
asset_user.id: asset_user.assets.values_list('id', flat=True)
for asset_user in asset_users
}
else:
assets_user_assets = asset_users.values_list('id', 'assets')
for i, asset_id in assets_user_assets:
assets_user_assets_map[i].add(asset_id)
for asset_user in asset_users: for asset_user in asset_users:
if not assets: if not assets:
assets = asset_user.assets.all() related_assets = asset_user.assets.all()
for asset in assets: else:
assets_map = {a.id: a for a in assets}
related_assets = [
assets_map.get(i) for i in assets_user_assets_map.get(asset_user.id) if i in assets_map
]
for asset in related_assets:
instance = asset_user.construct_to_authbook(asset) instance = asset_user.construct_to_authbook(asset)
instance.backend = cls.backend instance.backend = cls.backend
instances.append(instance) instances.append(instance)
......
...@@ -81,6 +81,11 @@ class AssetUserQuerySet(list): ...@@ -81,6 +81,11 @@ class AssetUserQuerySet(list):
queryset = self.filter_in(kwargs).filter_equal(kwargs) queryset = self.filter_in(kwargs).filter_equal(kwargs)
return queryset return queryset
def distinct(self):
items = list(set(self))
self[:] = items
return self
def __or__(self, other): def __or__(self, other):
self.extend(other) self.extend(other)
return self return self
# -*- coding: utf-8 -*-
#
import coreapi
from rest_framework import filters
from django.db.models import Q
from common.utils import dict_get_any, is_uuid, get_object_or_none
from .models import Node, Label
class AssetByNodeFilterBackend(filters.BaseFilterBackend):
fields = ['node', 'all']
def get_schema_fields(self, view):
return [
coreapi.Field(
name=field, location='query', required=False,
type='string', example='', description='', schema=None,
)
for field in self.fields
]
@staticmethod
def is_query_all(request):
query_all_arg = request.query_params.get('all')
show_current_asset_arg = request.query_params.get('show_current_asset')
query_all = query_all_arg == '1'
if show_current_asset_arg is not None:
query_all = show_current_asset_arg != '1'
return query_all
@staticmethod
def get_query_node(request):
node_id = dict_get_any(request.query_params, ['node', 'node_id'])
if not node_id:
return None, False
if is_uuid(node_id):
node = get_object_or_none(Node, id=node_id)
else:
node = get_object_or_none(Node, key=node_id)
return node, True
@staticmethod
def perform_query(pattern, queryset):
return queryset.filter(nodes__key__regex=pattern).distinct()
def filter_queryset(self, request, queryset, view):
node, has_query_arg = self.get_query_node(request)
if not has_query_arg:
return queryset
if node is None:
return queryset.none()
query_all = self.is_query_all(request)
if query_all:
pattern = node.get_all_children_pattern(with_self=True)
else:
pattern = node.get_children_key_pattern(with_self=True)
return self.perform_query(pattern, queryset)
class LabelFilterBackend(filters.BaseFilterBackend):
sep = '#'
query_arg = 'label'
def get_schema_fields(self, view):
example = self.sep.join(['os', 'linux'])
return [
coreapi.Field(
name=self.query_arg, location='query', required=False,
type='string', example=example, description=''
)
]
def get_query_labels(self, request):
labels_query = request.query_params.getlist(self.query_arg)
if not labels_query:
return None
q = None
for kv in labels_query:
if self.sep not in kv:
continue
key, value = kv.strip().split(self.sep)[:2]
if not all([key, value]):
continue
if q:
q |= Q(name=key, value=value)
else:
q = Q(name=key, value=value)
if not q:
return []
labels = Label.objects.filter(q, is_active=True)\
.values_list('id', flat=True)
return labels
def filter_queryset(self, request, queryset, view):
labels = self.get_query_labels(request)
if labels is None:
return queryset
if len(labels) == 0:
return queryset.none()
for label in labels:
queryset = queryset.filter(labels=label)
return queryset
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
@staticmethod
def perform_query(pattern, queryset):
return queryset.filter(asset__nodes__key__regex=pattern).distinct()
...@@ -4,7 +4,7 @@ from django import forms ...@@ -4,7 +4,7 @@ from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins import OrgModelForm from orgs.mixins.forms import OrgModelForm
from ..models import Asset, Node from ..models import Asset, Node
...@@ -29,9 +29,19 @@ class ProtocolForm(forms.Form): ...@@ -29,9 +29,19 @@ class ProtocolForm(forms.Form):
class AssetCreateForm(OrgModelForm): class AssetCreateForm(OrgModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.data:
return
nodes_field = self.fields['nodes'] nodes_field = self.fields['nodes']
nodes_field.choices = ((n.id, n.full_value) for n in if self.instance:
Node.get_queryset()) nodes_field.choices = [(n.id, n.full_value) for n in
self.instance.nodes.all()]
else:
nodes_field.choices = []
def add_nodes_initial(self, node):
nodes_field = self.fields['nodes']
nodes_field.choices.append((node.id, node.full_value))
nodes_field.initial = [node]
class Meta: class Meta:
model = Asset model = Asset
...@@ -42,7 +52,7 @@ class AssetCreateForm(OrgModelForm): ...@@ -42,7 +52,7 @@ class AssetCreateForm(OrgModelForm):
] ]
widgets = { widgets = {
'nodes': forms.SelectMultiple(attrs={ 'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Nodes') 'class': 'nodes-select2', 'data-placeholder': _('Nodes')
}), }),
'admin_user': forms.Select(attrs={ 'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user') 'class': 'select2', 'data-placeholder': _('Admin user')
...@@ -58,6 +68,8 @@ class AssetCreateForm(OrgModelForm): ...@@ -58,6 +68,8 @@ class AssetCreateForm(OrgModelForm):
'nodes': _("Node"), 'nodes': _("Node"),
} }
help_texts = { help_texts = {
'hostname': _('Only Numbers, letters, and characters ( {} ) '
'are allowed').format(" ".join(['.', '_', '@'])),
'admin_user': _( 'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,' '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' 'If asset is windows or other set any one, more see admin user left menu'
...@@ -68,6 +80,17 @@ class AssetCreateForm(OrgModelForm): ...@@ -68,6 +80,17 @@ class AssetCreateForm(OrgModelForm):
class AssetUpdateForm(OrgModelForm): class AssetUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.data:
return
nodes_field = self.fields['nodes']
if self.instance:
nodes_field.choices = ((n.id, n.full_value) for n in
self.instance.nodes.all())
else:
nodes_field.choices = []
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
...@@ -77,7 +100,7 @@ class AssetUpdateForm(OrgModelForm): ...@@ -77,7 +100,7 @@ class AssetUpdateForm(OrgModelForm):
] ]
widgets = { widgets = {
'nodes': forms.SelectMultiple(attrs={ 'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Node') 'class': 'nodes-select2', 'data-placeholder': _('Node')
}), }),
'admin_user': forms.Select(attrs={ 'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user') 'class': 'select2', 'data-placeholder': _('Admin user')
...@@ -93,6 +116,8 @@ class AssetUpdateForm(OrgModelForm): ...@@ -93,6 +116,8 @@ class AssetUpdateForm(OrgModelForm):
'nodes': _("Node"), 'nodes': _("Node"),
} }
help_texts = { help_texts = {
'hostname': _('Only Numbers, letters, and characters ( {} ) '
'are allowed').format(" ".join(['.', '_', '@'])),
'admin_user': _( 'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,' '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' 'If asset is windows or other set any one, more see admin user left menu'
......
...@@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError ...@@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import re import re
from orgs.mixins import OrgModelForm from orgs.mixins.forms import OrgModelForm
from ..models import CommandFilter, CommandFilterRule from ..models import CommandFilter, CommandFilterRule
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm'] __all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from orgs.mixins import OrgModelForm from orgs.mixins.forms import OrgModelForm
from ..models import Domain, Asset, Gateway from ..models import Domain, Asset, Gateway
from .user import PasswordAndKeyAuthForm from .user import PasswordAndKeyAuthForm
......
...@@ -4,7 +4,7 @@ from django import forms ...@@ -4,7 +4,7 @@ from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
from orgs.mixins import OrgModelForm from orgs.mixins.forms import OrgModelForm
from ..models import AdminUser, SystemUser from ..models import AdminUser, SystemUser
logger = get_logger(__file__) logger = get_logger(__file__)
......
# Generated by Django 2.1.7 on 2019-07-24 12:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0036_auto_20190716_1535'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='_become_pass',
field=models.CharField(blank=True, default='', max_length=128),
),
]
# Generated by Django 2.1.7 on 2019-09-11 08:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0037_auto_20190724_2002'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Protocol'),
),
]
# Generated by Django 2.1.7 on 2019-09-17 12:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0038_auto_20190911_1634'),
]
operations = [
migrations.AddField(
model_name='authbook',
name='is_active',
field=models.BooleanField(default=True, verbose_name='Is active'),
),
]
# Generated by Django 2.1.7 on 2019-09-17 12:56
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0039_authbook_is_active'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='authbook',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
]
# Generated by Django 2.1.7 on 2019-09-18 04:10
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0040_auto_20190917_2056'),
]
operations = [
migrations.CreateModel(
name='GatheredUser',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('username', models.CharField(blank=True, db_index=True, max_length=32, verbose_name='Username')),
('present', models.BooleanField(default=True, verbose_name='Present')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
],
options={'ordering': ['asset'], 'verbose_name': 'GatherUser'},
),
]
...@@ -9,3 +9,4 @@ from .cmd_filter import * ...@@ -9,3 +9,4 @@ from .cmd_filter import *
from .authbook import * from .authbook import *
from .utils import * from .utils import *
from .authbook import * from .authbook import *
from .gathered_user import *
...@@ -6,14 +6,13 @@ import uuid ...@@ -6,14 +6,13 @@ import uuid
import logging import logging
import random import random
from functools import reduce from functools import reduce
from collections import OrderedDict, defaultdict from collections import OrderedDict
from django.core.cache import cache
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .utils import Connectivity from .utils import Connectivity
from orgs.mixins import OrgModelMixin, OrgManager from orgs.mixins.models import OrgModelMixin, OrgManager
__all__ = ['Asset', 'ProtocolsMixin'] __all__ = ['Asset', 'ProtocolsMixin']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -32,7 +31,7 @@ def default_cluster(): ...@@ -32,7 +31,7 @@ def default_cluster():
def default_node(): def default_node():
try: try:
from .node import Node from .node import Node
root = Node.root() root = Node.org_root()
return root return root
except: except:
return None return None
...@@ -58,7 +57,7 @@ class ProtocolsMixin: ...@@ -58,7 +57,7 @@ class ProtocolsMixin:
PROTOCOL_CHOICES = ( PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'), (PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'), (PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet (beta)'), (PROTOCOL_TELNET, 'telnet'),
(PROTOCOL_VNC, 'vnc'), (PROTOCOL_VNC, 'vnc'),
) )
...@@ -103,39 +102,17 @@ class NodesRelationMixin: ...@@ -103,39 +102,17 @@ class NodesRelationMixin:
id = "" id = ""
_all_nodes_keys = None _all_nodes_keys = None
@classmethod
def get_all_nodes_keys(cls):
"""
:return: {asset.id: [node.key, ]}
"""
from .node import Node
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cached = cache.get(cache_key)
if cached:
return cached
assets = Asset.objects.all().only('id').prefetch_related(
models.Prefetch('nodes', queryset=Node.objects.all().only('key'))
)
assets_nodes_keys = {}
for asset in assets:
assets_nodes_keys[asset.id] = [n.key for n in asset.nodes.all()]
cache.set(cache_key, assets_nodes_keys, cls.CACHE_TIME)
return assets_nodes_keys
@classmethod
def expire_all_nodes_keys_cache(cls):
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cache.delete(cache_key)
def get_nodes(self): def get_nodes(self):
from .node import Node from .node import Node
nodes = self.nodes.all() or [Node.root()] nodes = self.nodes.all()
if not nodes:
nodes = Node.objects.filter(id=Node.org_root().id)
return nodes return nodes
def get_all_nodes(self, flat=False): def get_all_nodes(self, flat=False):
nodes = [] nodes = []
for node in self.get_nodes(): for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True) _nodes = node.get_ancestors(with_self=True)
nodes.append(_nodes) nodes.append(_nodes)
if flat: if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
...@@ -345,7 +322,6 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): ...@@ -345,7 +322,6 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
else: else:
_nodes = [Node.default_node()] _nodes = [Node.default_node()]
asset.nodes.set(_nodes) asset.nodes.set(_nodes)
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
logger.debug('Generate fake asset : %s' % asset.ip) logger.debug('Generate fake asset : %s' % asset.ip)
except IntegrityError: except IntegrityError:
print('Error continue') print('Error continue')
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgManager from orgs.mixins.models import OrgManager
from .base import AssetUser from .base import AssetUser
__all__ = ['AuthBook'] __all__ = ['AuthBook']
...@@ -13,7 +13,7 @@ __all__ = ['AuthBook'] ...@@ -13,7 +13,7 @@ __all__ = ['AuthBook']
class AuthBookQuerySet(models.QuerySet): class AuthBookQuerySet(models.QuerySet):
def latest_version(self): def latest_version(self):
return self.filter(is_latest=True) return self.filter(is_latest=True).filter(is_active=True)
class AuthBookManager(OrgManager): class AuthBookManager(OrgManager):
...@@ -24,6 +24,7 @@ class AuthBook(AssetUser): ...@@ -24,6 +24,7 @@ class AuthBook(AssetUser):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version')) is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
version = models.IntegerField(default=1, verbose_name=_('Version')) version = models.IntegerField(default=1, verbose_name=_('Version'))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)() objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
backend = "db" backend = "db"
...@@ -34,25 +35,25 @@ class AuthBook(AssetUser): ...@@ -34,25 +35,25 @@ class AuthBook(AssetUser):
class Meta: class Meta:
verbose_name = _('AuthBook') verbose_name = _('AuthBook')
def _set_latest(self): def set_to_latest(self):
self._remove_pre_obj_latest() self.remove_pre_latest()
self.is_latest = True self.is_latest = True
self.save() self.save()
def _get_pre_obj(self): def get_pre_latest(self):
pre_obj = self.__class__.objects.filter( pre_obj = self.__class__.objects.filter(
username=self.username, asset=self.asset username=self.username, asset=self.asset
).latest_version().first() ).latest_version().first()
return pre_obj return pre_obj
def _remove_pre_obj_latest(self): def remove_pre_latest(self):
pre_obj = self._get_pre_obj() pre_obj = self.get_pre_latest()
if pre_obj: if pre_obj:
pre_obj.is_latest = False pre_obj.is_latest = False
pre_obj.save() pre_obj.save()
def _set_version(self): def set_version(self):
pre_obj = self._get_pre_obj() pre_obj = self.get_pre_latest()
if pre_obj: if pre_obj:
self.version = pre_obj.version + 1 self.version = pre_obj.version + 1
else: else:
...@@ -60,8 +61,8 @@ class AuthBook(AssetUser): ...@@ -60,8 +61,8 @@ class AuthBook(AssetUser):
self.save() self.save()
def set_version_and_latest(self): def set_version_and_latest(self):
self._set_version() self.set_version()
self._set_latest() self.set_to_latest()
def get_related_assets(self): def get_related_assets(self):
return [self.asset] return [self.asset]
......
...@@ -15,7 +15,7 @@ from common.utils import ( ...@@ -15,7 +15,7 @@ from common.utils import (
) )
from common.validators import alphanumeric from common.validators import alphanumeric
from common import fields from common import fields
from orgs.mixins import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from .utils import private_key_validator, Connectivity from .utils import private_key_validator, Connectivity
signer = get_signer() signer = get_signer()
...@@ -26,7 +26,7 @@ logger = get_logger(__file__) ...@@ -26,7 +26,7 @@ logger = get_logger(__file__)
class AssetUser(OrgModelMixin): class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric]) username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
......
...@@ -7,7 +7,7 @@ from django.db import models ...@@ -7,7 +7,7 @@ from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin from orgs.mixins.models import OrgModelMixin
__all__ = [ __all__ = [
......
...@@ -9,7 +9,7 @@ import paramiko ...@@ -9,7 +9,7 @@ import paramiko
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from .base import AssetUser from .base import AssetUser
__all__ = ['Domain', 'Gateway'] __all__ = ['Domain', 'Gateway']
......
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
__all__ = ['GatheredUser']
class GatheredUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True,
verbose_name=_('Username'))
present = models.BooleanField(default=True, verbose_name=_("Present"))
date_created = models.DateTimeField(auto_now_add=True,
verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True,
verbose_name=_("Date updated"))
@property
def hostname(self):
return self.asset.hostname
@property
def ip(self):
return self.asset.ip
class Meta:
verbose_name = _('GatherUser')
ordering = ['asset']
def __str__(self):
return '{}: {}'.format(self.asset.hostname, self.username)
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import uuid import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin from orgs.mixins.models import OrgModelMixin
class Label(OrgModelMixin): class Label(OrgModelMixin):
......
This diff is collapsed.
...@@ -31,7 +31,7 @@ class AdminUser(AssetUser): ...@@ -31,7 +31,7 @@ class AdminUser(AssetUser):
become = models.BooleanField(default=True) become = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64) become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', max_length=128) _become_pass = models.CharField(default='', blank=True, max_length=128)
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}' CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
_prefer = "admin_user" _prefer = "admin_user"
...@@ -96,7 +96,7 @@ class SystemUser(AssetUser): ...@@ -96,7 +96,7 @@ class SystemUser(AssetUser):
PROTOCOL_CHOICES = ( PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'), (PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'), (PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet (beta)'), (PROTOCOL_TELNET, 'telnet'),
(PROTOCOL_VNC, 'vnc'), (PROTOCOL_VNC, 'vnc'),
) )
...@@ -148,16 +148,12 @@ class SystemUser(AssetUser): ...@@ -148,16 +148,12 @@ class SystemUser(AssetUser):
return True, None return True, None
def get_all_assets(self): def get_all_assets(self):
args = [Q(systemuser=self)] from assets.models import Node
pattern = set()
nodes_keys = self.nodes.all().values_list('key', flat=True) nodes_keys = self.nodes.all().values_list('key', flat=True)
for key in nodes_keys: assets_ids = set(self.assets.all().values_list('id', flat=True))
pattern.add(r'^{0}$|^{0}:'.format(key)) nodes_assets_ids = Node.get_nodes_all_assets_ids(nodes_keys)
pattern = '|'.join(list(pattern)) assets_ids.update(nodes_assets_ids)
if pattern: assets = Asset.objects.filter(id__in=assets_ids)
args.append(Q(nodes__key__regex=pattern))
args = reduce(lambda x, y: x | y, args)
assets = Asset.objects.filter(args).distinct()
return assets return assets
class Meta: class Meta:
......
...@@ -9,3 +9,4 @@ from .node import * ...@@ -9,3 +9,4 @@ from .node import *
from .domain import * from .domain import *
from .cmd_filter import * from .cmd_filter import *
from .asset_user import * from .asset_user import *
from .gathered_user import *
...@@ -6,7 +6,7 @@ from rest_framework import serializers ...@@ -6,7 +6,7 @@ from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Node, AdminUser from ..models import Node, AdminUser
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import AuthSerializer, AuthSerializerMixin from .base import AuthSerializer, AuthSerializerMixin
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import re
from rest_framework import serializers from rest_framework import serializers
from django.db.models import Prefetch from django.db.models import Prefetch
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Asset, Node, Label from ..models import Asset, Node, Label
from .base import ConnectivitySerializer from .base import ConnectivitySerializer
...@@ -91,6 +92,15 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -91,6 +92,15 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'org_name': {'label': _('Org name')} 'org_name': {'label': _('Org name')}
} }
@staticmethod
def validate_hostname(hostname):
pattern = r"^[\._@a-zA-Z0-9-]+$"
res = re.match(pattern, hostname)
if res is None:
msg = _("* The hostname contains characters that are not allowed")
raise serializers.ValidationError(msg)
return hostname
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
......
...@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _ ...@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import AuthBook, Asset from ..models import AuthBook, Asset
from ..backends import AssetUserManager from ..backends import AssetUserManager
from .base import ConnectivitySerializer, AuthSerializerMixin from .base import ConnectivitySerializer, AuthSerializerMixin
...@@ -53,6 +53,7 @@ class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -53,6 +53,7 @@ class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
if not validated_data.get("name") and validated_data.get("username"): if not validated_data.get("name") and validated_data.get("username"):
validated_data["name"] = validated_data["username"] validated_data["name"] = validated_data["username"]
instance = AssetUserManager.create(**validated_data) instance = AssetUserManager.create(**validated_data)
instance.set_version_and_latest()
return instance return instance
......
...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
from common.fields import ChoiceDisplayField from common.fields import ChoiceDisplayField
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import CommandFilter, CommandFilterRule, SystemUser from ..models import CommandFilter, CommandFilterRule, SystemUser
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
class CommandFilterSerializer(BulkOrgResourceModelSerializer): class CommandFilterSerializer(BulkOrgResourceModelSerializer):
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import Domain, Gateway from ..models import Domain, Gateway
from .base import AuthSerializerMixin from .base import AuthSerializerMixin
......
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from ..models import GatheredUser
class GatheredUserSerializer(OrgResourceModelSerializerMixin):
class Meta:
model = GatheredUser
fields = [
'id', 'asset', 'hostname', 'ip', 'username',
'present', 'date_created', 'date_updated'
]
read_only_fields = fields
labels = {
'hostname': _("Hostname"),
'ip': "IP"
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import Label from ..models import Label
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import Asset, Node from ..models import Asset, Node
...@@ -13,22 +13,23 @@ __all__ = [ ...@@ -13,22 +13,23 @@ __all__ = [
class NodeSerializer(BulkOrgResourceModelSerializer): class NodeSerializer(BulkOrgResourceModelSerializer):
assets_amount = serializers.IntegerField(read_only=True)
name = serializers.ReadOnlyField(source='value') name = serializers.ReadOnlyField(source='value')
value = serializers.CharField(
required=False, allow_blank=True, allow_null=True, label=_("value")
)
class Meta: class Meta:
model = Node model = Node
only_fields = ['id', 'key', 'value', 'org_id'] only_fields = ['id', 'key', 'value', 'org_id']
fields = only_fields + ['name', 'assets_amount'] fields = only_fields + ['name', 'full_value']
read_only_fields = [ read_only_fields = ['key', 'org_id']
'key', 'name', 'assets_amount', 'org_id',
]
def validate_value(self, data): def validate_value(self, data):
instance = self.instance if self.instance else Node.root() if not self.instance and not data:
children = instance.parent.get_children() return data
children_values = [node.value for node in children if node != instance] instance = self.instance
if data in children_values: siblings = instance.get_siblings()
if siblings.filter(value=data):
raise serializers.ValidationError( raise serializers.ValidationError(
_('The same level node name cannot be the same') _('The same level node name cannot be the same')
) )
......
...@@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from common.utils import ssh_pubkey_gen from common.utils import ssh_pubkey_gen
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser from ..models import SystemUser
from .base import AuthSerializer, AuthSerializerMixin from .base import AuthSerializer, AuthSerializerMixin
......
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
#
from .utils import *
from .common import *
from .admin_user_connectivity import *
from .asset_connectivity import *
from .asset_user_connectivity import *
from .gather_asset_users import *
from .gather_asset_hardware_info import *
from .push_system_user import *
from .system_user_connectivity import *
# ~*~ coding: utf-8 ~*~
from celery import shared_task
from django.utils.translation import ugettext as _
from django.core.cache import cache
from common.utils import get_logger
from ops.celery.decorator import register_as_period_task
from ..models import AdminUser
from .utils import clean_hosts
from .asset_connectivity import test_asset_connectivity_util
from . import const
logger = get_logger(__file__)
__all__ = [
'test_admin_user_connectivity_util', 'test_admin_user_connectivity_manual',
'test_admin_user_connectivity_period'
]
@shared_task(queue="ansible")
def test_admin_user_connectivity_util(admin_user, task_name):
"""
Test asset admin user can connect or not. Using ansible api do that
:param admin_user:
:param task_name:
:return:
"""
assets = admin_user.get_related_assets()
hosts = clean_hosts(assets)
if not hosts:
return {}
summary = test_asset_connectivity_util(hosts, task_name)
return summary
@shared_task(queue="ansible")
@register_as_period_task(interval=3600)
def test_admin_user_connectivity_period():
"""
A period task that update the ansible task period
"""
if const.PERIOD_TASK_ENABLED:
logger.debug('Period task off, skip')
return
key = '_JMS_TEST_ADMIN_USER_CONNECTIVITY_PERIOD'
prev_execute_time = cache.get(key)
if prev_execute_time:
logger.debug("Test admin user connectivity, less than 40 minutes, skip")
return
cache.set(key, 1, 60*40)
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
cache.set(key, 1, 60*40)
@shared_task(queue="ansible")
def test_admin_user_connectivity_manual(admin_user):
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
return True
# ~*~ coding: utf-8 ~*~
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from ..models.utils import Connectivity
from . import const
from .utils import clean_hosts
logger = get_logger(__file__)
__all__ = ['test_asset_connectivity_util', 'test_asset_connectivity_manual']
@shared_task(queue="ansible")
def test_asset_connectivity_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Test assets connectivity")
hosts = clean_hosts(assets)
if not hosts:
return {}
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
)
created_by = assets[0].org_id
for k, 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,
)
raw, summary = task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
for asset in assets:
if asset.hostname in results_summary.get('dark', {}).keys():
asset.connectivity = Connectivity.unreachable()
elif asset.hostname in results_summary.get('contacted', {}).keys():
asset.connectivity = Connectivity.reachable()
else:
asset.connectivity = Connectivity.unknown()
return results_summary
@shared_task(queue="ansible")
def test_asset_connectivity_manual(asset):
task_name = _("Test assets connectivity: {}").format(asset)
summary = test_asset_connectivity_util([asset], task_name=task_name)
if summary.get('dark'):
return False, summary['dark']
else:
return True, ""
# ~*~ coding: utf-8 ~*~
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from . import const
from .utils import check_asset_can_run_ansible
logger = get_logger(__file__)
__all__ = [
'test_asset_user_connectivity_util', 'test_asset_users_connectivity_manual',
'get_test_asset_user_connectivity_tasks',
]
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(queue="ansible")
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
"""
:param asset_user: <AuthBook>对象
:param task_name:
:param run_as_admin:
:return:
"""
from ops.utils import update_or_create_ansible_task
if not check_asset_can_run_ansible(asset_user.asset):
return
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
if not tasks:
logger.debug("No tasks ")
return
args = (task_name,)
kwargs = {
'hosts': [asset_user.asset], 'tasks': tasks,
'pattern': 'all', 'options': const.TASK_OPTIONS,
'created_by': asset_user.org_id,
}
if run_as_admin:
kwargs["run_as_admin"] = True
else:
kwargs["run_as"] = asset_user.username
task, created = update_or_create_ansible_task(*args, **kwargs)
raw, summary = task.run()
asset_user.set_connectivity(summary)
@shared_task(queue="ansible")
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
"""
:param asset_users: <AuthBook>对象
"""
for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user)
test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
# -*- coding: utf-8 -*-
#
from celery import shared_task
__all__ = ['add_nodes_assets_to_system_users']
@shared_task
def add_nodes_assets_to_system_users(nodes_keys, system_users):
from ..models import Node
assets = Node.get_nodes_all_assets(nodes_keys).values_list('id', flat=True)
for system_user in system_users:
system_user.assets.add(*tuple(assets))
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import os
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
PERIOD_TASK_ENABLED = os.environ.get("PERIOD_TASK", "on") == 'on'
UPDATE_ASSETS_HARDWARE_TASKS = [ UPDATE_ASSETS_HARDWARE_TASKS = [
{ {
'name': "setup", 'name': "setup",
...@@ -79,3 +83,22 @@ CONNECTIVITY_CHOICES = ( ...@@ -79,3 +83,22 @@ CONNECTIVITY_CHOICES = (
(CONN_UNKNOWN, _("Unknown")), (CONN_UNKNOWN, _("Unknown")),
) )
GATHER_ASSET_USERS_TASKS = [
{
"name": "gather host users",
"action": {
"module": 'getent',
"args": "database=passwd"
},
},
]
GATHER_ASSET_USERS_TASKS_WINDOWS = [
{
"name": "gather windows host users",
"action": {
"module": 'win_shell',
"args": "net user"
}
}
]
# -*- coding: utf-8 -*-
#
import json
import re
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import (
capacity_convert, sum_capacity, get_logger
)
from . import const
from .utils import clean_hosts
logger = get_logger(__file__)
disk_pattern = re.compile(r'^hd|sd|xvd|vd|nv')
__all__ = [
'update_assets_hardware_info_util', 'update_asset_hardware_info_manual',
'update_assets_hardware_info_period',
]
def set_assets_hardware_info(assets, result, **kwargs):
"""
Using ops task run result, to update asset info
@shared_task must be exit, because we using it as a task callback, is must
be a celery task also
:param assets:
:param result:
:param kwargs: {task_name: ""}
:return:
"""
result_raw = result[0]
assets_updated = []
success_result = result_raw.get('ok', {})
for asset in assets:
hostname = asset.hostname
info = success_result.get(hostname, {})
info = info.get('setup', {}).get('ansible_facts', {})
if not info:
logger.error(_("Get asset info failed: {}").format(hostname))
continue
___vendor = info.get('ansible_system_vendor', 'Unknown')
___model = info.get('ansible_product_name', 'Unknown')
___sn = info.get('ansible_product_serial', 'Unknown')
for ___cpu_model in info.get('ansible_processor', []):
if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
break
else:
___cpu_model = 'Unknown'
___cpu_model = ___cpu_model[:48]
___cpu_count = info.get('ansible_processor_count', 0)
___cpu_cores = info.get('ansible_processor_cores', None) or \
len(info.get('ansible_processor', []))
___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
___memory = '%s %s' % capacity_convert(
'{} MB'.format(info.get('ansible_memtotal_mb'))
)
disk_info = {}
for dev, dev_info in info.get('ansible_devices', {}).items():
if disk_pattern.match(dev) and dev_info['removable'] == '0':
disk_info[dev] = dev_info['size']
___disk_total = '%.1f %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info)
# ___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')
___hostname_raw = info.get('ansible_hostname', 'Unknown')
for k, v in locals().items():
if k.startswith('___'):
setattr(asset, k.strip('_'), v)
asset.save()
assets_updated.append(asset)
return assets_updated
@shared_task
def update_assets_hardware_info_util(assets, task_name=None):
"""
Using ansible api to update asset hardware info
:param assets: asset seq
:param task_name: task_name running
:return: result summary ['contacted': {}, 'dark': {}]
"""
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Update some assets hardware info")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hosts = clean_hosts(assets)
if not hosts:
return {}
created_by = str(assets[0].org_id)
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
)
result = task.run()
set_assets_hardware_info(assets, result)
return True
@shared_task(queue="ansible")
def update_asset_hardware_info_manual(asset):
task_name = _("Update asset hardware info: {}").format(asset.hostname)
update_assets_hardware_info_util(
[asset], task_name=task_name
)
@shared_task(queue="ansible")
def update_assets_hardware_info_period():
"""
Update asset hardware period task
:return:
"""
if not const.PERIOD_TASK_ENABLED:
logger.debug("Period task disabled, update assets hardware info pass")
return
# ~*~ coding: utf-8 ~*~
import re
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from orgs.utils import tmp_to_org
from common.utils import get_logger
from ..models import GatheredUser, Node
from .utils import clean_hosts
from . import const
__all__ = ['gather_asset_users', 'gather_nodes_asset_users']
logger = get_logger(__name__)
space = re.compile('\s+')
ignore_login_shell = re.compile(r'nologin$|sync$|shutdown$|halt$')
def parse_linux_result_to_users(result):
task_result = {}
for task_name, raw in result.items():
res = raw.get('ansible_facts', {}).get('getent_passwd')
if res:
task_result = res
break
if not task_result or not isinstance(task_result, dict):
return []
users = []
for username, attr in task_result.items():
if ignore_login_shell.search(attr[-1]):
continue
users.append(username)
return users
def parse_windows_result_to_users(result):
task_result = []
for task_name, raw in result.items():
res = raw.get('stdout_lines', {})
if res:
task_result = res
break
if not task_result:
return []
users = []
for i in range(4):
task_result.pop(0)
for i in range(2):
task_result.pop()
for line in task_result:
user = space.split(line)
if user[0]:
users.append(user[0])
return users
def add_asset_users(assets, results):
assets_map = {a.hostname: a for a in assets}
parser_map = {
'linux': parse_linux_result_to_users,
'windows': parse_windows_result_to_users
}
assets_users_map = {}
for platform, platform_results in results.items():
for hostname, res in platform_results.items():
parse = parser_map.get(platform)
users = parse(res)
logger.debug('Gathered host users: {} {}'.format(hostname, users))
asset = assets_map.get(hostname)
if not asset:
continue
assets_users_map[asset] = users
for asset, users in assets_users_map.items():
with tmp_to_org(asset.org_id):
GatheredUser.objects.filter(asset=asset, present=True)\
.update(present=False)
for username in users:
defaults = {'asset': asset, 'username': username, 'present': True}
GatheredUser.objects.update_or_create(
defaults=defaults, asset=asset, username=username,
)
@shared_task(queue="ansible")
def gather_asset_users(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Gather assets users")
assets = clean_hosts(assets)
if not assets:
return
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.GATHER_ASSET_USERS_TASKS
},
'windows': {
'hosts': [],
'tasks': const.GATHER_ASSET_USERS_TASKS_WINDOWS
}
}
for asset in assets:
hosts_list = hosts_category['windows']['hosts'] if asset.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(asset)
results = {'linux': defaultdict(dict), 'windows': defaultdict(dict)}
for k, value in hosts_category.items():
if not value['hosts']:
continue
_task_name = '{}: {}'.format(task_name, k)
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=value['hosts'][0].org_id,
)
raw, summary = task.run()
results[k].update(raw['ok'])
add_asset_users(assets, results)
@shared_task(queue="ansible")
def gather_nodes_asset_users(nodes_key):
assets = Node.get_nodes_all_assets(nodes_key)
assets_groups_by_100 = [assets[i:i+100] for i in range(0, len(assets), 100)]
for _assets in assets_groups_by_100:
gather_asset_users(_assets)
# ~*~ coding: utf-8 ~*~
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import encrypt_password, get_logger
from . import const
from .utils import clean_hosts_by_protocol, clean_hosts
logger = get_logger(__file__)
__all__ = [
'push_system_user_util', 'push_system_user_to_assets',
'push_system_user_to_assets_manual', 'push_system_user_a_asset_manual',
]
def get_push_linux_system_user_tasks(system_user):
tasks = [
{
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present'.format(
system_user.username, system_user.shell,
),
}
},
{
'name': 'Add group {}'.format(system_user.username),
'action': {
'module': 'group',
'args': 'name={} state=present'.format(
system_user.username,
),
}
},
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
},
'register': 'home_existed'
},
{
'name': "Set home dir permission",
'action': {
'module': 'file',
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
},
'when': 'home_existed.stat.exists == true'
}
]
if system_user.password:
tasks.append({
'name': 'Set {} password'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
system_user.username, system_user.shell,
encrypt_password(system_user.password, salt="K3mIlKK"),
),
}
})
if system_user.public_key:
tasks.append({
'name': 'Set {} authorized key'.format(system_user.username),
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
system_user.username, system_user.public_key
)
}
})
if system_user.sudo:
sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n')
sudo_list = sudo.split('\n')
sudo_tmp = []
for s in sudo_list:
sudo_tmp.append(s.strip(','))
sudo = ','.join(sudo_tmp)
tasks.append({
'name': 'Set {} sudo setting'.format(system_user.username),
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
system_user.username, sudo,
)
}
})
return tasks
def get_push_windows_system_user_tasks(system_user):
tasks = []
if not system_user.password:
return tasks
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
@shared_task(queue="ansible")
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 or rdp: {}").format(system_user.name)
logger.info(msg)
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(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,
created_by=system_user.org_id,
)
task.run()
@shared_task(queue="ansible")
def push_system_user_to_assets_manual(system_user):
assets = system_user.get_all_assets()
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name=task_name)
@shared_task(queue="ansible")
def push_system_user_a_asset_manual(system_user, asset):
task_name = _("Push system users to asset: {} => {}").format(
system_user.name, asset
)
return push_system_user_util(system_user, [asset], task_name=task_name)
@shared_task(queue="ansible")
def push_system_user_to_assets(system_user, assets):
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name)
# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# @after_app_shutdown_clean_periodic
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)
\ No newline at end of file
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from ..models import SystemUser
from . import const
from .utils import clean_hosts, clean_hosts_by_protocol
logger = get_logger(__name__)
__all__ = [
'test_system_user_connectivity_util', 'test_system_user_connectivity_manual',
'test_system_user_connectivity_period', 'test_system_user_connectivity_a_asset',
]
@shared_task(queue="ansible")
def test_system_user_connectivity_util(system_user, assets, task_name):
"""
Test system cant connect his assets or not.
:param system_user:
:param assets:
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
hosts = clean_hosts(assets)
if not hosts:
return {}
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
)
for k, 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,
)
raw, summary = task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
system_user.set_connectivity(results_summary)
return results_summary
@shared_task(queue="ansible")
def test_system_user_connectivity_manual(system_user):
task_name = _("Test system user connectivity: {}").format(system_user)
assets = system_user.get_all_assets()
return test_system_user_connectivity_util(system_user, assets, task_name)
@shared_task(queue="ansible")
def test_system_user_connectivity_a_asset(system_user, asset):
task_name = _("Test system user connectivity: {} => {}").format(
system_user, asset
)
return test_system_user_connectivity_util(system_user, [asset], task_name)
@shared_task(queue="ansible")
def test_system_user_connectivity_period():
if not const.PERIOD_TASK_ENABLED:
logger.debug("Period task disabled, test system user connectivity pass")
return
system_users = SystemUser.objects.all()
for system_user in system_users:
task_name = _("Test system user connectivity period: {}").format(system_user)
assets = system_user.get_all_assets()
test_system_user_connectivity_util(system_user, assets, task_name)
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from common.utils import get_logger
logger = get_logger(__file__)
__all__ = [
'check_asset_can_run_ansible', 'clean_hosts', 'clean_hosts_by_protocol'
]
def check_asset_can_run_ansible(asset):
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
return False
if not asset.is_support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
return False
return True
def clean_hosts(assets):
clean_assets = []
for asset in assets:
if not check_asset_can_run_ansible(asset):
continue
clean_assets.append(asset)
if not clean_assets:
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
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
text-align: center; text-align: center;
} }
#assetTree2.ztree * { #asset_modal_tree.ztree * {
background-color: #f8fafb; background-color: white;
} }
#assetTree2.ztree { #asset_modal_tree.ztree {
background-color: #f8fafb; background-color: white;
} }
</style> </style>
...@@ -29,7 +29,8 @@ ...@@ -29,7 +29,8 @@
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager "> <div class="file-manager ">
<div id="assetTree2" class="ztree"> <div id="asset_modal_tree" class="ztree">
{% trans 'Loading' %} ...
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
...@@ -55,12 +56,77 @@ ...@@ -55,12 +56,77 @@
</div> </div>
<script> <script>
var zTree2, asset_table2 = 0; function syncTableSelectedAssetToSelect2(table) {
function initTable2() { var assets = table.selected;
if(asset_table2){ var options = [];
return var select2Id = assetModalOption.select2Id;
$(select2Id + ' option').each(function (i, v) {
options.push(v.value)
});
table.selected_rows.forEach(function (i) {
var name = i.hostname + '(' + i.ip + ')';
var option = new Option(name, i.id, false, true);
if (options.indexOf(i.id) === -1) {
$(select2Id).append(option).trigger('change');
}
});
$(select2Id).val(assets).trigger('change');
}
// 解决input框中的资产和弹出表格中资产的显示不一致
function syncSelectedAssetsToModalTable(assetModalTable) {
var select2Id = assetModalOption.select2Id;
var inputAssets = $(select2Id).val();
var selectedAssets = assetModalTable.selected.concat();
// input assets无,table assets选中,则取消勾选(再次click)
if (selectedAssets.length !== 0) {
$.each(selectedAssets, function (index, assetId) {
if ($.inArray(assetId, inputAssets) === -1) {
$('#' + assetId).trigger('click'); // 取消勾选
}
});
} }
// input assets有,table assets没选,则选中(click)
if (inputAssets) {
assetModalTable.selected = inputAssets;
$.each(inputAssets, function (index, assetId) {
var dom = document.getElementById(assetId);
if (dom !== null) {
var selected = dom.parentElement.parentElement.className.indexOf('selected')
}
if (selected === -1) {
$('#' + assetId).trigger('click');
}
});
}
}
defaultOnAssetModalConfirm = syncTableSelectedAssetToSelect2;
defaultOnModalTableDone = syncSelectedAssetsToModalTable;
var assetModalOption = {
selectStyle: 'multi',
select2Id: '#id_assets',
onModalTableDone: defaultOnModalTableDone,
onModalTreeDone: null,
onModalConfirm: defaultOnAssetModalConfirm,
};
var assetModalTable, assetModalTree = null;
function initAssetModalTable() {
if(assetModalTable){
return
}
if (assetModalOption.selectStyle === 'single') {
$('.ipt_check_all').addClass('hidden')
}
var options = { var options = {
ele: $('#asset_list_modal_table'), ele: $('#asset_list_modal_table'),
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1', ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
...@@ -68,22 +134,26 @@ function initTable2() { ...@@ -68,22 +134,26 @@ function initTable2() {
{data: "id"}, {data: "hostname" }, {data: "ip" } {data: "id"}, {data: "hostname" }, {data: "ip" }
], ],
lengthMenu: [[10, 25, 50], [10, 25, 50]], lengthMenu: [[10, 25, 50], [10, 25, 50]],
pageLength: 10 pageLength: 10,
select_style: assetModalOption.selectStyle
}; };
asset_table2 = jumpserver.initServerSideDataTable(options); assetModalTable = jumpserver.initServerSideDataTable(options);
return asset_table2 if (assetModalOption.onModalTableDone) {
assetModalOption.onModalTableDone(assetModalTable);
}
return assetModalTable
} }
function onNodeSelected2(event, treeNode) { function onModalTreeNodeSelected(event, treeNode) {
var url = asset_table2.ajax.url(); var url = assetModalTable.ajax.url();
url = setUrlParam(url, "node_id", treeNode.meta.node.id); url = setUrlParam(url, "node_id", treeNode.meta.node.id);
url = setUrlParam(url, "show_current_asset", ""); url = setUrlParam(url, "show_current_asset", "");
asset_table2.ajax.url(url); assetModalTable.ajax.url(url);
asset_table2.ajax.reload(); assetModalTable.ajax.reload();
} }
function initTree2() { function initModalTree() {
var url = '{% url 'api-assets:node-children-tree' %}?assets=0'; var url = '{% url 'api-assets:node-children-tree' %}?assets=0';
var setting = { var setting = {
view: { view: {
...@@ -102,17 +172,34 @@ function initTree2() { ...@@ -102,17 +172,34 @@ function initTree2() {
type: 'get' type: 'get'
}, },
callback: { callback: {
onSelected: onNodeSelected2 onSelected: onModalTreeNodeSelected
} }
}; };
zTree2 = $.fn.zTree.init($("#assetTree2"), setting); $.get(url, function(data, status){
$.fn.zTree.init($("#asset_modal_tree"), setting);
assetModalTree = $.fn.zTree.getZTreeObj("assetTree2");
if (assetModalOption.onModalTreeDone) {
assetModalOption.onModalTreeDone(assetModalTree);
}
return assetModalTree;
});
}
function setAssetModalOptions(options) {
assetModalOption = options;
} }
$(document).ready(function(){ $(document).ready(function(){
}).on('show.bs.modal', function () { }).on('show.bs.modal', function () {
initTable2(); initAssetModalTable();
initTree2(); initModalTree();
}).on('click', '#btn_asset_modal_confirm', function () {
if (assetModalOption.onModalConfirm) {
assetModalOption.onModalConfirm(assetModalTable, assetModalTree);
}
$("#asset_list_modal").modal('hide');
}) })
</script> </script>
{% endblock %} {% endblock %}
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager" id="tree-node-id"> <div class="file-manager" id="tree-node-id">
<div id="{% block treeID %}nodeTree{% endblock %}" class="ztree"> <div id="{% block treeID %}nodeTree{% endblock %}" class="ztree">
{% trans 'Loading' %} ...
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
...@@ -73,7 +74,6 @@ function initNodeTree(options) { ...@@ -73,7 +74,6 @@ function initNodeTree(options) {
if (options.showAssets) { if (options.showAssets) {
treeUrl = setUrlParam(treeUrl, 'assets', '1') treeUrl = setUrlParam(treeUrl, 'assets', '1')
} }
var asyncTreeUrl = setUrlParam(treeUrl, 'refresh', '0');
var setting = { var setting = {
view: { view: {
dblClickExpand: false, dblClickExpand: false,
...@@ -86,7 +86,7 @@ function initNodeTree(options) { ...@@ -86,7 +86,7 @@ function initNodeTree(options) {
}, },
async: { async: {
enable: true, enable: true,
url: asyncTreeUrl, url: treeUrl,
autoParam: ["id=key", "name=n", "level=lv"], autoParam: ["id=key", "name=n", "level=lv"],
type: 'get' type: 'get'
}, },
...@@ -114,9 +114,15 @@ function initNodeTree(options) { ...@@ -114,9 +114,15 @@ function initNodeTree(options) {
$.get(treeUrl, function (data, status) { $.get(treeUrl, function (data, status) {
zTree = $.fn.zTree.init($("#nodeTree"), setting, data); zTree = $.fn.zTree.init($("#nodeTree"), setting, data);
rootNodeAddDom(zTree, function () { rootNodeAddDom(zTree, function () {
treeUrl = setUrlParam(treeUrl, 'refresh', '1'); const url = '{% url 'api-assets:refresh-nodes-cache' %}';
initNodeTree(options); requestApi({
treeUrl = setUrlParam(treeUrl, 'refresh', '0'); url: url,
method: 'GET',
flash_message: false,
success: function () {
initNodeTree(options);
}
});
}); });
inited = true; inited = true;
}); });
...@@ -236,7 +242,8 @@ function onBodyMouseDown(event){ ...@@ -236,7 +242,8 @@ function onBodyMouseDown(event){
} }
function onRename(event, treeId, treeNode, isCancel){ function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id); var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}"
.replace("{{ DEFAULT_PK }}", current_node_id);
var data = {"value": treeNode.name}; var data = {"value": treeNode.name};
if (isCancel){ if (isCancel){
return return
...@@ -247,10 +254,13 @@ function onRename(event, treeId, treeNode, isCancel){ ...@@ -247,10 +254,13 @@ function onRename(event, treeId, treeNode, isCancel){
method: "PATCH", method: "PATCH",
success_message: "{% trans 'Rename success' %}", success_message: "{% trans 'Rename success' %}",
success: function () { success: function () {
treeNode.name = treeNode.name + ' (' + treeNode.meta.node.assets_amount + ')'; var assets_amount = treeNode.meta.node.assets_amount;
if (!assets_amount) {
assets_amount = 0;
}
treeNode.name = treeNode.name + ' (' + assets_amount + ')';
zTree.updateNode(treeNode); zTree.updateNode(treeNode);
console.log("Success: " + treeNode.name) },
}
}) })
} }
......
...@@ -119,7 +119,7 @@ function autoLoginModeProtocol() { ...@@ -119,7 +119,7 @@ function autoLoginModeProtocol() {
$(sudo_id).closest('.form-group').addClass('hidden'); $(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden'); $(shell_id).closest('.form-group').addClass('hidden');
} }
else if (protocol === 'telnet (beta)') { else if (protocol === 'telnet') {
$('.auth-fields').removeClass('hidden'); $('.auth-fields').removeClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden'); $(auto_generate_key).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden'); $(private_key_id).closest('.form-group').addClass('hidden');
...@@ -165,7 +165,7 @@ function manualLoginModeProtocol() { ...@@ -165,7 +165,7 @@ function manualLoginModeProtocol() {
$(sudo_id).closest('.form-group').addClass('hidden'); $(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden'); $(shell_id).closest('.form-group').addClass('hidden');
} }
else if (protocol === 'telnet (beta)') { else if (protocol === 'telnet') {
$('.auth-fields').addClass('hidden'); $('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden'); $(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden'); $(password_id).closest('.form-group').addClass('hidden');
......
...@@ -88,9 +88,9 @@ ...@@ -88,9 +88,9 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="nodes-select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes %} {% for node in nodes %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option> <option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.full_value }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
...@@ -140,7 +140,8 @@ function replaceNodeAssetsAdminUser(nodes) { ...@@ -140,7 +140,8 @@ function replaceNodeAssetsAdminUser(nodes) {
jumpserver.nodes_selected = {}; jumpserver.nodes_selected = {};
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2() var url = "{% url 'api-assets:node-list' %}";
nodesSelect2Init(".nodes-select2", url)
.on('select2:select', function(evt) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text; jumpserver.nodes_selected[data.id] = data.text;
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<div class="col-md-2 col-md-offset-2" style="text-align: right">{{ fm.name }}</div> <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-6">{{ fm.port }}</div>
<div class="col-md-1" style="padding: 6px 0"> <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-danger btn-xs btn-protocol btn-delete"><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> <a class="btn btn-primary btn-xs btn-protocol btn-add" style="display: none"><span class="fa fa-plus"></span></a>
</div> </div>
</div> </div>
...@@ -97,7 +97,7 @@ function format(item) { ...@@ -97,7 +97,7 @@ function format(item) {
function protocolBtnShow() { function protocolBtnShow() {
$(".btn-protocol.btn-add").hide(); $(".btn-protocol.btn-add").hide();
$(".btn-protocol.btn-add:last").show(); $(".btn-protocol.btn-add:last").show();
var btnDel = $(".btn-protocol.btn-del"); var btnDel = $(".btn-protocol.btn-delete");
if (btnDel.length === 1) { if (btnDel.length === 1) {
btnDel.addClass("disabled") btnDel.addClass("disabled")
} else { } else {
...@@ -110,6 +110,8 @@ $(document).ready(function () { ...@@ -110,6 +110,8 @@ $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
allowClear: true allowClear: true
}); });
var url = "{% url 'api-assets:node-list' %}";
nodesSelect2Init(".nodes-select2", url);
$(".labels").select2({ $(".labels").select2({
allowClear: true, allowClear: true,
templateSelection: format templateSelection: format
...@@ -138,7 +140,7 @@ $(document).ready(function () { ...@@ -138,7 +140,7 @@ $(document).ready(function () {
protocolRef.trigger("change") protocolRef.trigger("change")
} }
}) })
.on("click", ".btn-protocol.btn-del", function () { .on("click", ".btn-protocol.btn-delete", function () {
$(this).parent().parent().remove(); $(this).parent().parent().remove();
protocolBtnShow() protocolBtnShow()
}) })
......
...@@ -195,10 +195,7 @@ ...@@ -195,10 +195,7 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="nodes-select2 groups" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
...@@ -211,7 +208,7 @@ ...@@ -211,7 +208,7 @@
{% for node in asset.nodes.all %} {% for node in asset.nodes.all %}
<tr> <tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td> <td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.full_value }}</b></td>
<td> <td>
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button> <button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
</td> </td>
...@@ -291,7 +288,9 @@ function refreshAssetHardware() { ...@@ -291,7 +288,9 @@ function refreshAssetHardware() {
$(document).ready(function () { $(document).ready(function () {
$('.select2.groups').select2().on('select2:select', function(evt) { var url = "{% url 'api-assets:node-list' %}";
nodesSelect2Init(".nodes-select2", url)
.on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text; jumpserver.nodes_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) { }).on('select2:unselect', function(evt) {
......
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button> <button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels"> <ul class="dropdown-menu labels">
{% for label in labels %} {% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li> <li><a style="font-weight: bolder">{{ label.name }}#{{ label.value }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
...@@ -124,7 +124,6 @@ ...@@ -124,7 +124,6 @@
</div> </div>
</div> </div>
{% include 'assets/_node_tree.html' %}
{% include 'assets/_asset_update_modal.html' %} {% include 'assets/_asset_update_modal.html' %}
{% include 'assets/_asset_import_modal.html' %} {% include 'assets/_asset_import_modal.html' %}
{% include 'assets/_asset_list_modal.html' %} {% include 'assets/_asset_list_modal.html' %}
...@@ -172,9 +171,13 @@ function initTable() { ...@@ -172,9 +171,13 @@ function initTable() {
], ],
ajax_url: '{% url "api-assets:asset-list" %}', ajax_url: '{% url "api-assets:asset-list" %}',
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "id"}, {data: "hostname"}, {data: "ip"},
{data: "cpu_cores", orderable: false}, {data: "cpu_cores", orderable: false},
{data: "connectivity", orderable: false}, {data: "id", orderable: false } {
data: "connectivity",
orderable: false,
width: '60px'
}, {data: "id", orderable: false}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
...@@ -227,6 +230,33 @@ function onNodeSelected(event, treeNode) { ...@@ -227,6 +230,33 @@ function onNodeSelected(event, treeNode) {
asset_table.ajax.reload(); asset_table.ajax.reload();
} }
function onAssetModalConfirmAddAssetToNode(table) {
var assets_selected = table.selected;
if (!current_node_id) {
return
}
var data = {'assets': assets_selected};
var success = function () {
table.selected = [];
table.ajax.reload()
};
var url = '';
if (update_node_action === "move") {
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
} else {
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
}
requestApi({
'url': url,
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
}
$(document).ready(function(){ $(document).ready(function(){
initTable(); initTable();
initTree(); initTree();
...@@ -237,9 +267,15 @@ $(document).ready(function(){ ...@@ -237,9 +267,15 @@ $(document).ready(function(){
else{ else{
$('#show_current_asset').css('display', 'inline-block'); $('#show_current_asset').css('display', 'inline-block');
} }
var modalOption = {
onModalConfirm: onAssetModalConfirmAddAssetToNode,
onModalTableDone: null
};
setAssetModalOptions(modalOption);
}) })
.on('click', '.labels li', function () { .on('click', '.labels li', function () {
var val = $(this).text(); var val = 'label:' + $(this).text();
$("#asset_list_table_filter input").val(val); $("#asset_list_table_filter input").val(val);
asset_table.search(val).draw(); asset_table.search(val).draw();
}) })
...@@ -430,7 +466,6 @@ $(document).ready(function(){ ...@@ -430,7 +466,6 @@ $(document).ready(function(){
} }
function doRemove() { function doRemove() {
var nodes = zTree.getSelectedNodes();
if (!current_node_id) { if (!current_node_id) {
return return
} }
...@@ -442,9 +477,10 @@ $(document).ready(function(){ ...@@ -442,9 +477,10 @@ $(document).ready(function(){
var success = function () { var success = function () {
asset_table.ajax.reload() asset_table.ajax.reload()
}; };
var url = "{% url 'api-assets:node-remove-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
requestApi({ requestApi({
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/', 'url': url,
'method': 'PUT', 'method': 'PUT',
'body': JSON.stringify(data), 'body': JSON.stringify(data),
'success': success 'success': success
...@@ -472,32 +508,7 @@ $(document).ready(function(){ ...@@ -472,32 +508,7 @@ $(document).ready(function(){
} }
$(".ipt_check_all").prop("checked", false) $(".ipt_check_all").prop("checked", false)
}) })
.on('click', '#btn_asset_modal_confirm', function () { .on('hidden.bs.modal', '#asset_list_modal', function () {
var assets_selected = asset_table2.selected;
if (!current_node_id) {
return
}
var data = {'assets': assets_selected};
var success = function () {
asset_table2.selected = [];
asset_table2.ajax.reload()
};
var url = '';
if (update_node_action === "move") {
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
} else {
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
}
requestApi({
'url': url,
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
}).on('hidden.bs.modal', '#asset_list_modal', function () {
window.location.reload(); window.location.reload();
}).on('click', '#menu_asset_add', function () { }).on('click', '#menu_asset_add', function () {
update_node_action = "add" update_node_action = "add"
......
...@@ -103,7 +103,7 @@ $(document).ready(function(){ ...@@ -103,7 +103,7 @@ $(document).ready(function(){
.on('click', '.btn-delete', function () { .on('click', '.btn-delete', function () {
var $this = $(this); var $this = $(this);
var $data_table = $('#cmd_filter_rule_list_table').DataTable(); var $data_table = $('#cmd_filter_rule_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html(); var name = $(this).closest("tr").find(":nth-child(2)").html();
var uid = $this.data('uid'); var uid = $this.data('uid');
var the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=object.id pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); var the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=object.id pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url); objectDelete($this, name, the_url);
......
...@@ -28,25 +28,6 @@ $(document).ready(function () { ...@@ -28,25 +28,6 @@ $(document).ready(function () {
}).on('click', '.select2-selection__rendered', function (e) { }).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault(); e.preventDefault();
$("#asset_list_modal").modal(); $("#asset_list_modal").modal();
initSelectedAssets2Table();
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
var options = [];
$('#id_assets option').each(function (i, v) {
options.push(v.value)
});
asset_table2.selected_rows.forEach(function (i) {
var name = i.hostname + '(' + i.ip + ')';
var option = new Option(name, i.id, false, true);
if (options.indexOf(i.id) === -1) {
$('#id_assets').append(option).trigger('change');
}
});
$('.select2').val(assets).trigger('change');
$("#asset_list_modal").modal('hide');
}) })
.on("submit", "form", function (evt) { .on("submit", "form", function (evt) {
evt.preventDefault(); evt.preventDefault();
......
...@@ -118,7 +118,7 @@ $(document).ready(function(){ ...@@ -118,7 +118,7 @@ $(document).ready(function(){
.on('click', '.btn-delete', function () { .on('click', '.btn-delete', function () {
var $this = $(this); var $this = $(this);
var $data_table = $('#domain_list_table').DataTable(); var $data_table = $('#domain_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html(); var name = $(this).closest("tr").find(":nth-child(2)").html();
var uid = $this.data('uid'); var uid = $this.data('uid');
var the_url = '{% url "api-assets:gateway-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); var the_url = '{% url "api-assets:gateway-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url); objectDelete($this, name, the_url);
......
...@@ -32,24 +32,6 @@ $(document).ready(function () { ...@@ -32,24 +32,6 @@ $(document).ready(function () {
}).on('click', '.select2-selection__rendered', function (e) { }).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault(); e.preventDefault();
$("#asset_list_modal").modal(); $("#asset_list_modal").modal();
initSelectedAssets2Table();
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
var options = [];
$('#id_assets option').each(function (i, v) {
options.push(v.value)
});
asset_table2.selected_rows.forEach(function (i) {
var name = i.hostname + '(' + i.ip + ')';
var option = new Option(name, i.id, false, true);
if (options.indexOf(i.id) === -1) {
$('#id_assets').append(option).trigger('change');
}
});
$('#id_assets').val(assets).trigger('change');
$("#asset_list_modal").modal('hide');
}) })
.on("submit", "form", function (evt) { .on("submit", "form", function (evt) {
evt.preventDefault(); evt.preventDefault();
......
...@@ -88,10 +88,7 @@ ...@@ -88,10 +88,7 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="nodes-select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
...@@ -104,7 +101,7 @@ ...@@ -104,7 +101,7 @@
{% for node in system_user.nodes.all|sort %} {% for node in system_user.nodes.all|sort %}
<tr> <tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td> <td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.full_value }}</b></td>
<td> <td>
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button> <button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
</td> </td>
...@@ -156,6 +153,8 @@ jumpserver.nodes_selected = {}; ...@@ -156,6 +153,8 @@ jumpserver.nodes_selected = {};
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2() $('.select2').select2()
var url = "{% url 'api-assets:node-list' %}";
nodesSelect2Init(".nodes-select2", url)
.on('select2:select', function(evt) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text; jumpserver.nodes_selected[data.id] = data.text;
......
...@@ -21,9 +21,10 @@ ...@@ -21,9 +21,10 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var treeUrl = "{% url 'api-perms:my-nodes-as-tree' %}?&cache_policy=1"; var treeUrl = "{% url 'api-perms:my-nodes-children-as-tree' %}?&cache_policy=1";
var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1"; var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1";
var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1&all=1'; var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1&all=1';
var systemUsersUrl = "{% url 'api-perms:my-asset-system-users' asset_id=DEFAULT_PK %}?cache_policy=1";
var showAssetHref = false; // Need input default true var showAssetHref = false; // Need input default true
var actions = { var actions = {
targets: 4, createdCell: function (td, cellData) { targets: 4, createdCell: function (td, cellData) {
...@@ -33,7 +34,6 @@ var actions = { ...@@ -33,7 +34,6 @@ var actions = {
}}; }};
$(document).ready(function () { $(document).ready(function () {
initTree(); initTree();
initTable();
}).on('click', '.labels li', function () { }).on('click', '.labels li', function () {
var val = $(this).text(); var val = $(this).text();
$("#user_assets_table_filter input").val(val); $("#user_assets_table_filter input").val(val);
......
# coding:utf-8 # coding:utf-8
from django.urls import path from django.urls import path, re_path
from rest_framework_nested import routers from rest_framework_nested import routers
# from rest_framework.routers import DefaultRouter # from rest_framework.routers import DefaultRouter
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from common import api as capi
from .. import api from .. import api
app_name = 'assets' app_name = 'assets'
router = BulkRouter() router = BulkRouter()
router.register(r'assets', api.AssetViewSet, 'asset') router.register(r'assets', api.AssetViewSet, 'asset')
router.register(r'admin-user', api.AdminUserViewSet, 'admin-user') router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
router.register(r'system-user', api.SystemUserViewSet, 'system-user') router.register(r'system-users', api.SystemUserViewSet, 'system-user')
router.register(r'labels', api.LabelViewSet, 'label') router.register(r'labels', api.LabelViewSet, 'label')
router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'domain', api.DomainViewSet, 'domain') router.register(r'domains', api.DomainViewSet, 'domain')
router.register(r'gateway', api.GatewayViewSet, 'gateway') router.register(r'gateways', api.GatewayViewSet, 'gateway')
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter') router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
router.register(r'asset-user', api.AssetUserViewSet, 'asset-user') router.register(r'asset-users', api.AssetUserViewSet, 'asset-user')
router.register(r'asset-user-info', api.AssetUserExportViewSet, 'asset-user-info') router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter') cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
urlpatterns = [ urlpatterns = [
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
path('asset/update/select/',
api.AssetBulkUpdateSelectAPI.as_view(), name='asset-bulk-update-select'),
path('assets/<uuid:pk>/refresh/', path('assets/<uuid:pk>/refresh/',
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'), api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
path('assets/<uuid:pk>/alive/', path('assets/<uuid:pk>/alive/',
...@@ -35,36 +35,36 @@ urlpatterns = [ ...@@ -35,36 +35,36 @@ urlpatterns = [
path('assets/<uuid:pk>/gateway/', path('assets/<uuid:pk>/gateway/',
api.AssetGatewayApi.as_view(), name='asset-gateway'), api.AssetGatewayApi.as_view(), name='asset-gateway'),
path('asset-user/auth-info/', path('asset-users/auth-info/',
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'), api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
path('asset-user/test-connective/', path('asset-users/test-connective/',
api.AssetUserTestConnectiveApi.as_view(), name='asset-user-connective'), api.AssetUserTestConnectiveApi.as_view(), name='asset-user-connective'),
path('admin-user/<uuid:pk>/nodes/', path('admin-users/<uuid:pk>/nodes/',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'), api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
path('admin-user/<uuid:pk>/auth/', path('admin-users/<uuid:pk>/auth/',
api.AdminUserAuthApi.as_view(), name='admin-user-auth'), api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
path('admin-user/<uuid:pk>/connective/', path('admin-users/<uuid:pk>/connective/',
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'), api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
path('admin-user/<uuid:pk>/assets/', path('admin-users/<uuid:pk>/assets/',
api.AdminUserAssetsListView.as_view(), name='admin-user-assets'), api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
path('system-user/<uuid:pk>/auth-info/', path('system-users/<uuid:pk>/auth-info/',
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'), api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/auth-info/', path('system-users/<uuid:pk>/assets/<uuid:aid>/auth-info/',
api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
path('system-user/<uuid:pk>/assets/', path('system-users/<uuid:pk>/assets/',
api.SystemUserAssetsListView.as_view(), name='system-user-assets'), api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
path('system-user/<uuid:pk>/push/', path('system-users/<uuid:pk>/push/',
api.SystemUserPushApi.as_view(), name='system-user-push'), api.SystemUserPushApi.as_view(), name='system-user-push'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/push/', path('system-users/<uuid:pk>/assets/<uuid:aid>/push/',
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'), api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/test/', path('system-users/<uuid:pk>/assets/<uuid:aid>/test/',
api.SystemUserTestAssetConnectivityApi.as_view(), name='system-user-test-to-asset'), api.SystemUserTestAssetConnectivityApi.as_view(), name='system-user-test-to-asset'),
path('system-user/<uuid:pk>/connective/', path('system-users/<uuid:pk>/connective/',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
path('system-user/<uuid:pk>/cmd-filter-rules/', path('system-users/<uuid:pk>/cmd-filter-rules/',
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
...@@ -86,13 +86,17 @@ urlpatterns = [ ...@@ -86,13 +86,17 @@ urlpatterns = [
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'), api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
path('nodes/<uuid:pk>/test-connective/', path('nodes/<uuid:pk>/test-connective/',
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'), api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
path('nodes/refresh-assets-amount/',
api.RefreshAssetsAmount.as_view(), name='refresh-assets-amount'),
path('gateway/<uuid:pk>/test-connective/', path('nodes/cache/', api.RefreshNodesCacheApi.as_view(), name='refresh-nodes-cache'),
path('gateways/<uuid:pk>/test-connective/',
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
] ]
urlpatterns += router.urls + cmd_filter_router.urls old_version_urlpatterns = [
re_path('(?P<resource>admin-user|system-user|domain|gateway|cmd-filter|asset-user)/.*', capi.redirect_plural_name_api)
]
urlpatterns += router.urls + cmd_filter_router.urls + old_version_urlpatterns
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment