mirror of https://github.com/jumpserver/jumpserver
Merge to dev (#1051)
* [Update] 修改 success message, 添加资产组时可以添加资产 * [Update] system user form add label * [Update] set default cluster * [Update] 修改一些翻译 * [Bugfix] 修复重置密码bug * [Bugfix] 默认default cluster * [Bugfix] 用户添加报错 * 修改tab样式 * [Bugfix] 修复了一些显示上的bug * 修复全选按钮在搜索后仍然选择全部的问题 * [Bugfix] 修复以下bug 1. 查看执行历史异常 2. 用户授权资产页显示message * [Update] api 返回platform, 并增加web terminal nav * [Feature] 添加setting页面 * [Feature] 添加basic settings * [Update] 修改翻译 * [Update] 修改config * [Update] 启动加载common setting * [Bugfix] 修复cluster创建的bug * [Bugfix] 修复title显示Jumpserver * [Bugfix] setting tables not found * [Bugfix] settings add option * [Feature] 添加后端paging * [Bugfix] 资产列表选择别的页会报错 * [Update] check all 只选择当前页面 * [Bugfix] user login ip * [Bugfix] for login ip * [Bugfix] 修复资产列表显示bug * [Remove] labels * [Bugfix] task运行失败,因为tasks没有设置 * [Feature] 增加标签 * [Bugfix] 读取不到prefix * For storage * [Change] 修改部分翻译 * [Update] 启用ldap移动位置 * [Update] 修改翻译 * [Feature] 支持es存储命令 * Update README.md * [Feature] 添加es支持 * [update] 修改用户创建时 姓名和用户名的位置 * [Update] 修改install.md * [Update] remote default PAGE_SIZE stting * [Feature] terminal config load * [Feature] es support * [Update] 修改requirement * [Update] 修改requirements * [Update] 修改dictfiled * [Fix] 修改Logger * [Bugfix] 倒序显示 * [Update] 修改默认头像和logo * [Update] 修改django-celery-beat的版本 * [Feature] 添加修改用户密码api * add logo test * [Bugfix] 修复一些bug * [Update] 修改copyrite * [Update] 修改copyright * Update ISSUE_TEMPLATE.md * [Update] 修改禁止排序的颜色 * [Feature] 标签管理功能 * [Bugfix] git status * [Model] 修改create_by字段 * [Update] 修改位置 * [Update] 修改签名md5算法 * [Feature] 资产列表标签搜索 * [Feature] 添加资产详情标签 * [Bugfix] 修复资产搜索bug * [Update] ansible disk bug * [Update] ansible disk bug * [Bugfix] 修复获取kvmcpu的bug * [Bugfix] 修复bsd获取cpu数量bug * [Bugfix] 修改翻译 * [Bugfix] 资产model 太长 * [Bugfix] 修改项目结构描述 修正"项目多语言目录" * Update project_structure.md * [Update] add debug log * refactor: rename folder i18n * [Feature] 添加链接token * [Feature] Label 删除修改 * [Update] 修改部分翻译 * [Update] 修改小bug * [Update] 修复获取资产信息异常bug * [Bugfix] 修复系统用户上传秘钥的bug * [Update] 修改获取资产信息产生的异常 * [Update] 删除部分资产属性 * [Bugfix] 资产批量便捷 * [Update] 修改认证 * [Feature] 支持popover * [Feature] tree * [Feature] 添加资产树 * [Feature] 使用ztree * [Feature] tree增删功能 * [Bugfix] 修复组详情bug * [Bugfix] 修复组详情bug * [Bugfix] 修改创建label时报错的bug * [Bugfix] 修改label api bug * [Update] 去掉资产组添加 * [Update] 修改ztrr * Update README.md * [Update] 修改资产创建 * [Bugfix] 修复ldap认证bug * [Update] 修改一处翻译 * [Update] 更改授权规则前commit * [Abandon] ... * Update README.md * Update README.md * Update README.md * [Feature] 完成资产授权和资产添加 * [Update] 修改授权 * [Bugfix] 修改创建系统用户的bug * feat: rdp support * [Update] 拆分asset api module * [Update] 资产列表选中和移除资产 * [Feature] 更改perms api * [Update] 使用资产树,去掉集群和资产组 * [Update] 修改系统用户推送,拆分assets的部分模块 * [Update] 完成树形改造 * [Update] 完成资产书 * [Update] 修改资产model * ubuntu16.04 deb_requirements.txt update (#1007) * Update run server.py (#915) Fix for not callable error when config.py not exists * [Update]一些修改 * [Update] 修改初始 * feat: replay setting page and api * 增加隐藏树功能 * [Update] 修改翻译 * 对齐菜单文字。修改英文 * feat: update app setting * fix: app get replay storage * [Update] 修改文案 * [Docs] 初始化doc * [Bugfix] 用户csv导入编码问题 * [Update] 修改设置的一些require * [Bugfix] 修复管理用户无法查看的bug * [Update] 修改授权api, windows资产只有rdp协议,linux只有ssh协议 * [Update] terminal可以更改名称 * [Update] 统一copyright * [Update] 修改文档 * [Bugfix] 修复资产禁用还可以登录 * [Update] 修改文案 * [Update] 支持拖拽更新 * [Bugfix] 修复bug,修改celery beat版本依赖 * [Update] 修改一些小问题 * 添加普通用户使用内容 * [Update] 修改一些文案 * Update README.md * Update README.md * Update README.md * 用户列表 * [Update] 修改一些bug和文案 * [Delete] 删除build 页面 * [Update] 修改conf * [Update] bugfix * [Update] 更新文档地址 * [Update] 修改部分翻译和文档 * [Update] 修改一些bug * [Update] 修改链接 * [Update] 增加批量终端session api * [Update] 修改Node value唯一 * [Bugfix] 修复首页无法显示数据的bug * feat: s3 replay file get * feat: update * [Update] 修改bugpull/1053/head^2
parent
5c8dd5676c
commit
c2abd58dcb
|
@ -1,7 +1,7 @@
|
|||
[简述你的问题]
|
||||
|
||||
##### 使用版本
|
||||
[请提供你使用的Jumpserver版本 0.3.2 或 0.4.0]
|
||||
[请提供你使用的Jumpserver版本 0.3.2 或 0.5.0]
|
||||
|
||||
##### 问题复现步骤
|
||||
1. [步骤1]
|
||||
|
|
|
@ -29,4 +29,5 @@ media
|
|||
celerybeat.pid
|
||||
django.db
|
||||
celerybeat-schedule.db
|
||||
static
|
||||
data/static
|
||||
_build/
|
||||
|
|
30
README.md
30
README.md
|
@ -5,10 +5,18 @@
|
|||
[![Ansible](https://img.shields.io/badge/ansible-2.2.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
|
||||
[![Paramiko](https://img.shields.io/badge/paramiko-2.1.2-green.svg?style=plastic)](http://www.paramiko.org/)
|
||||
|
||||
Jumpserver is a open source proxy server, developed by `Python` and `Django`, aim to help
|
||||
companies to efficiently user, assets, authority and audit management
|
||||
|
||||
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理
|
||||
----
|
||||
|
||||
Jumpserver是全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
|
||||
|
||||
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||
|
||||
Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
|
||||
|
||||
改变世界,从一点点开始。
|
||||
|
||||
----
|
||||
|
||||
### Feature 功能
|
||||
- Auth 统一认证
|
||||
|
@ -24,9 +32,16 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互
|
|||
* Python 3.6
|
||||
* Django 1.11
|
||||
|
||||
### Install 安装
|
||||
### 快速启动
|
||||
|
||||
[详细安装](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
|
||||
```
|
||||
$ docker run -p 8080:80 -p 2222:2222 jumpserver/jumpserver:0.5.0-beta2
|
||||
```
|
||||
更多见 [Dockerfile](https://github.com/jumpserver/Dockerfile.git)
|
||||
|
||||
### 详细安装步骤
|
||||
|
||||
[文档](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
|
||||
|
||||
|
||||
### Usage 使用
|
||||
|
@ -70,6 +85,11 @@ demo使用了开发者模式,并发只能为1
|
|||
|
||||
参见 https://github.com/jumpserver/jumpserver/milestone/2
|
||||
|
||||
### SDK
|
||||
|
||||
- python: https://github.com/jumpserver/jumpserver-python-sdk
|
||||
- java: https://github.com/KaiJunYan/jumpserver-java-sdk.git
|
||||
|
||||
### Docs 开发者文档
|
||||
|
||||
|
||||
|
|
|
@ -1,297 +0,0 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2017 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q, Count
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.mixins import CustomFilterMixin
|
||||
from common.utils import get_logger
|
||||
from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
|
||||
get_user_granted_assets
|
||||
from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser
|
||||
from . import serializers
|
||||
from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \
|
||||
test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \
|
||||
test_system_user_connectability_manual
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
ordering_fields = ("hostname", "ip", "port", "cluster", "type", "env", "cpu_cores")
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
cluster_id = self.request.query_params.get('cluster_id')
|
||||
asset_group_id = self.request.query_params.get('asset_group_id')
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
system_user_id = self.request.query_params.get('system_user_id')
|
||||
|
||||
if cluster_id:
|
||||
queryset = queryset.filter(cluster__id=cluster_id)
|
||||
if asset_group_id:
|
||||
queryset = queryset.filter(groups__id=asset_group_id)
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
assets_direct = [asset.id for asset in admin_user.asset_set.all()]
|
||||
clusters = [cluster.id for cluster in admin_user.cluster_set.all()]
|
||||
queryset = queryset.filter(Q(cluster__id__in=clusters)|Q(id__in=assets_direct))
|
||||
if system_user_id:
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
clusters = system_user.get_clusters()
|
||||
queryset = queryset.filter(cluster__in=clusters)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserAssetListView(generics.ListAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
assets_granted = get_user_granted_assets(self.request.user)
|
||||
queryset = self.queryset.filter(
|
||||
id__in=[asset.id for asset in assets_granted]
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Asset group api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
queryset = AssetGroup.objects.all().annotate(asset_count=Count("assets"))
|
||||
serializer_class = serializers.AssetGroupSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class GroupUpdateAssetsApi(generics.RetrieveUpdateAPIView):
|
||||
"""
|
||||
Asset group, update it's asset member
|
||||
"""
|
||||
queryset = AssetGroup.objects.all()
|
||||
serializer_class = serializers.GroupUpdateAssetsSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class GroupAddAssetsApi(generics.UpdateAPIView):
|
||||
queryset = AssetGroup.objects.all()
|
||||
serializer_class = serializers.GroupUpdateAssetsSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
group = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
assets = serializer.validated_data['assets']
|
||||
group.assets.add(*tuple(assets))
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({'error': serializer.errors}, status=400)
|
||||
|
||||
|
||||
class ClusterViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Cluster api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
queryset = Cluster.objects.all()
|
||||
serializer_class = serializers.ClusterSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class ClusterTestAssetsAliveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test cluster asset can connect using admin user or not
|
||||
"""
|
||||
queryset = Cluster.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
cluster = self.get_object()
|
||||
admin_user = cluster.admin_user
|
||||
test_admin_user_connectability_manual.delay(admin_user)
|
||||
return Response("Task has been send, seen left assets status")
|
||||
|
||||
|
||||
class ClusterAddAssetsApi(generics.UpdateAPIView):
|
||||
queryset = Cluster.objects.all()
|
||||
serializer_class = serializers.ClusterUpdateAssetsSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
cluster = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
assets = serializer.validated_data['assets']
|
||||
for asset in assets:
|
||||
asset.cluster = cluster
|
||||
asset.save()
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({'error': serializer.errors}, status=400)
|
||||
|
||||
|
||||
class AdminUserViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Admin user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AdminUserAddClustersApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserUpdateClusterSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
clusters = serializer.validated_data['clusters']
|
||||
for cluster in clusters:
|
||||
cluster.admin_user = admin_user
|
||||
cluster.save()
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({'error': serializer.errors}, status=400)
|
||||
|
||||
|
||||
class SystemUserViewSet(BulkModelViewSet):
|
||||
"""
|
||||
System user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class AssetListUpdateApi(CustomFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
Asset bulk update api
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Get system user auth info
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
data = {
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
'password': system_user.password,
|
||||
'private_key': system_user.private_key,
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Refresh asset hardware info
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
summary = update_asset_hardware_info_manual(asset)[1]
|
||||
logger.debug("Refresh summary: {}".format(summary))
|
||||
if summary.get('dark'):
|
||||
return Response(summary['dark'].values(), status=501)
|
||||
else:
|
||||
return Response({"msg": "ok"})
|
||||
|
||||
|
||||
class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
ok, msg = test_asset_connectability_manual(asset)
|
||||
if ok:
|
||||
return Response({"msg": "pong"})
|
||||
else:
|
||||
return Response({"error": msg}, status=502)
|
||||
|
||||
|
||||
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
test_admin_user_connectability_manual.delay(admin_user)
|
||||
return Response({"msg": "Task created"})
|
||||
|
||||
|
||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
push_system_user_to_cluster_assets_manual.delay(system_user)
|
||||
return Response({"msg": "Task created"})
|
||||
|
||||
|
||||
class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
test_system_user_connectability_manual.delay(system_user)
|
||||
return Response({"msg": "Task created"})
|
|
@ -0,0 +1,5 @@
|
|||
from .admin_user import *
|
||||
from .asset import *
|
||||
from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
|
@ -0,0 +1,76 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from django.db import transaction
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..models import AdminUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import test_admin_user_connectability_manual
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AdminUserViewSet', 'ReplaceNodesAdminUserApi', 'AdminUserTestConnectiveApi'
|
||||
]
|
||||
|
||||
|
||||
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Admin user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.ReplaceNodeAdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
nodes = serializer.validated_data['nodes']
|
||||
assets = []
|
||||
for node in nodes:
|
||||
assets.extend([asset.id for asset in node.get_all_assets()])
|
||||
|
||||
with transaction.atomic():
|
||||
Asset.objects.filter(id__in=assets).update(admin_user=admin_user)
|
||||
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({'error': serializer.errors}, status=400)
|
||||
|
||||
|
||||
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
test_admin_user_connectability_manual.delay(admin_user)
|
||||
return Response({"msg": "Task created"})
|
|
@ -0,0 +1,112 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import generics
|
||||
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.shortcuts import get_object_or_404
|
||||
from django.db.models import Q
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
|
||||
NodePermissionUtil
|
||||
from ..models import Asset, SystemUser, AdminUser, Node
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
test_asset_connectability_manual
|
||||
from ..utils import LabelFilter
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
|
||||
]
|
||||
|
||||
|
||||
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
queryset = queryset.filter(admin_user=admin_user)
|
||||
if node_id:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
if not node.is_root():
|
||||
queryset = queryset.filter(nodes__key__startswith=node.key).distinct()
|
||||
return queryset
|
||||
|
||||
|
||||
class UserAssetListView(generics.ListAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
assets_granted = NodePermissionUtil.get_user_assets(self.request.user).keys()
|
||||
queryset = self.queryset.filter(
|
||||
id__in=[asset.id for asset in assets_granted]
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
Asset bulk update api
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Refresh asset hardware info
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
summary = update_asset_hardware_info_manual(asset)[1]
|
||||
logger.debug("Refresh summary: {}".format(summary))
|
||||
if summary.get('dark'):
|
||||
return Response(summary['dark'].values(), status=501)
|
||||
else:
|
||||
return Response({"msg": "ok"})
|
||||
|
||||
|
||||
class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
ok, msg = test_asset_connectability_manual(asset)
|
||||
if ok:
|
||||
return Response({"msg": "pong"})
|
||||
else:
|
||||
return Response({"error": msg}, status=502)
|
|
@ -0,0 +1,38 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..models import Label
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['LabelViewSet']
|
||||
|
||||
|
||||
class LabelViewSet(BulkModelViewSet):
|
||||
queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.LabelSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
if request.query_params.get("distinct"):
|
||||
self.serializer_class = serializers.LabelDistinctSerializer
|
||||
self.queryset = self.queryset.values("name").distinct()
|
||||
return super().list(request, *args, **kwargs)
|
|
@ -0,0 +1,119 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework import generics, mixins
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from ..hands import IsSuperUser
|
||||
from ..models import Node
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||
'NodeAddChildrenApi',
|
||||
]
|
||||
|
||||
|
||||
class NodeViewSet(BulkModelViewSet):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
child_key = Node.root().get_next_child_key()
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not request.data.get("value"):
|
||||
request.data["value"] = _("New node {}").format(
|
||||
Node.root().get_next_child_key().split(":")[-1]
|
||||
)
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
value = request.data.get("value")
|
||||
node = instance.create_child(value=value)
|
||||
return Response(
|
||||
{"id": node.id, "key": node.key, "value": node.value},
|
||||
status=201,
|
||||
)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
if self.request.query_params.get("all"):
|
||||
children = instance.get_all_children()
|
||||
else:
|
||||
children = instance.get_children()
|
||||
response = [{"id": node.id, "key": node.key, "value": node.value} for node in children]
|
||||
return Response(response, status=200)
|
||||
|
||||
|
||||
class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.NodeAddChildrenSerializer
|
||||
instance = None
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
nodes_id = request.data.get("nodes")
|
||||
children = [get_object_or_none(Node, id=pk) for pk in nodes_id]
|
||||
for node in children:
|
||||
if not node:
|
||||
continue
|
||||
node.parent = instance
|
||||
node.save()
|
||||
return Response("OK")
|
||||
|
||||
|
||||
class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
assets = serializer.validated_data.get('assets')
|
||||
instance = self.get_object()
|
||||
instance.assets.add(*tuple(assets))
|
||||
|
||||
|
||||
class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
assets = serializer.validated_data.get('assets')
|
||||
instance = self.get_object()
|
||||
if instance != Node.root():
|
||||
instance.assets.remove(*tuple(assets))
|
|
@ -0,0 +1,85 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsSuperUserOrAppUser
|
||||
from ..models import SystemUser
|
||||
from .. import serializers
|
||||
from ..tasks import push_system_user_to_assets_manual, \
|
||||
test_system_user_connectability_manual
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi'
|
||||
]
|
||||
|
||||
|
||||
class SystemUserViewSet(BulkModelViewSet):
|
||||
"""
|
||||
System user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Get system user auth info
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
data = {
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
'password': system_user.password,
|
||||
'private_key': system_user.private_key,
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
|
||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
push_system_user_to_assets_manual.delay(system_user)
|
||||
return Response({"msg": "Task created"})
|
||||
|
||||
|
||||
class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
test_system_user_connectability_manual.delay(system_user)
|
||||
return Response({"msg": "Task created"})
|
|
@ -1,379 +0,0 @@
|
|||
# coding:utf-8
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import Cluster, Asset, AssetGroup, AdminUser, SystemUser
|
||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'type', 'comment',
|
||||
'cluster', 'groups', 'status', 'env', 'is_active',
|
||||
'admin_user'
|
||||
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
||||
'cluster': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select cluster')}),
|
||||
'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select admin user')}),
|
||||
'port': forms.TextInput()
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
'port': '* required',
|
||||
'cluster': '* required',
|
||||
'admin_user': _('Host level admin user, If not set using cluster admin user default')
|
||||
}
|
||||
|
||||
def clean_admin_user(self):
|
||||
cluster = self.cleaned_data.get('cluster')
|
||||
admin_user = self.cleaned_data.get('admin_user')
|
||||
if not admin_user and (cluster and not cluster.admin_user):
|
||||
raise forms.ValidationError(_("You need set a admin user if cluster not have"))
|
||||
return self.cleaned_data['admin_user']
|
||||
|
||||
|
||||
class AssetUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'groups', "cluster", 'is_active',
|
||||
'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no',
|
||||
'cabinet_pos', 'number', 'comment', 'admin_user',
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
||||
'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _("Default using cluster admin user")})
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
'port': '* required',
|
||||
'cluster': '* required',
|
||||
'admin_user': _('Host level admin user, If not set using cluster admin user default')
|
||||
}
|
||||
|
||||
def clean_admin_user(self):
|
||||
cluster = self.cleaned_data.get('cluster')
|
||||
admin_user = self.cleaned_data.get('admin_user')
|
||||
if not admin_user and (cluster and not cluster.admin_user):
|
||||
raise forms.ValidationError(_("You need set a admin user if cluster not have"))
|
||||
return self.cleaned_data['admin_user']
|
||||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
required=True,
|
||||
help_text='* required',
|
||||
label=_('Select assets'),
|
||||
queryset=Asset.objects.all(),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Select assets')
|
||||
}
|
||||
)
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
label=_('Port'),
|
||||
required=False,
|
||||
min_value=1,
|
||||
max_value=65535,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'groups', "cluster",
|
||||
'type', 'env',
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
||||
}
|
||||
|
||||
def save(self, commit=True):
|
||||
changed_fields = []
|
||||
for field in self._meta.fields:
|
||||
if self.data.get(field) is not None:
|
||||
changed_fields.append(field)
|
||||
|
||||
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
||||
if k in changed_fields}
|
||||
assets = cleaned_data.pop('assets')
|
||||
groups = cleaned_data.pop('groups', [])
|
||||
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
|
||||
assets.update(**cleaned_data)
|
||||
if groups:
|
||||
for asset in assets:
|
||||
asset.groups.set(groups)
|
||||
return assets
|
||||
|
||||
|
||||
class AssetGroupForm(forms.ModelForm):
|
||||
# See AdminUserForm comment same it
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
queryset=Asset.objects.all(),
|
||||
label=_('Asset'),
|
||||
required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
initial = kwargs.get('initial', {})
|
||||
initial.update({
|
||||
'assets': instance.assets.all(),
|
||||
})
|
||||
kwargs['initial'] = initial
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
group = super().save(commit=commit)
|
||||
assets= self.cleaned_data['assets']
|
||||
group.assets.set(assets)
|
||||
return group
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
fields = [
|
||||
"name", "comment",
|
||||
]
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
}
|
||||
|
||||
|
||||
class ClusterForm(forms.ModelForm):
|
||||
system_users = forms.ModelMultipleChoiceField(
|
||||
queryset=SystemUser.objects.all(),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select system users')}
|
||||
),
|
||||
label=_('System users'),
|
||||
required=False,
|
||||
help_text=_("Selected system users will be create at cluster assets"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = ['name', "bandwidth", "operator", 'contact', 'admin_user', 'system_users',
|
||||
'phone', 'address', 'intranet', 'extranet', 'comment']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'intranet': forms.Textarea(attrs={'placeholder': 'IP段之间用逗号隔开,如:192.168.1.0/24,192.168.1.0/24'}),
|
||||
'extranet': forms.Textarea(attrs={'placeholder': 'IP段之间用逗号隔开,如:201.1.32.1/24,202.2.32.1/24'})
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'admin_user': _("Cluster level admin user"),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('instance', None):
|
||||
initial = kwargs.get('initial', {})
|
||||
initial['system_users'] = kwargs['instance'].systemuser_set.all()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=commit)
|
||||
system_users = self.cleaned_data['system_users']
|
||||
instance.systemuser_set.set(system_users)
|
||||
return instance
|
||||
|
||||
|
||||
class AdminUserForm(forms.ModelForm):
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(
|
||||
widget=forms.PasswordInput, max_length=128,
|
||||
strip=True, required=False,
|
||||
help_text=_('Password or private key password'),
|
||||
label=_("Password"),
|
||||
)
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False, label=_("Private key"))
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
admin_user = super().save(commit=commit)
|
||||
password = self.cleaned_data['password']
|
||||
private_key = self.cleaned_data['private_key_file']
|
||||
public_key = None
|
||||
|
||||
if not password:
|
||||
password = None
|
||||
|
||||
if private_key:
|
||||
public_key = ssh_pubkey_gen(private_key, password=password)
|
||||
|
||||
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
|
||||
return admin_user
|
||||
|
||||
def clean_private_key_file(self):
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
password = self.cleaned_data['password']
|
||||
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read()
|
||||
if not validate_ssh_private_key(private_key, password):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return private_key
|
||||
return private_key_file
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data.get('private_key_file', '')
|
||||
|
||||
if not password and not private_key_file:
|
||||
raise forms.ValidationError(_(
|
||||
'Password and private key file must be input one'
|
||||
))
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = ['name', 'username', 'password',
|
||||
'private_key_file', 'comment']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
}
|
||||
|
||||
|
||||
class SystemUserForm(forms.ModelForm):
|
||||
# Admin user assets define, let user select, save it in form not in view
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=False,
|
||||
max_length=128, strip=True, label=_("Password"))
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False, label=_("Private key"))
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super().save()
|
||||
password = self.cleaned_data.get('password', None)
|
||||
private_key_file = self.cleaned_data.get('private_key_file')
|
||||
auto_generate_key = self.cleaned_data.get('auto_generate_key')
|
||||
private_key = None
|
||||
public_key = None
|
||||
|
||||
if auto_generate_key:
|
||||
logger.info('Auto set system user auth')
|
||||
system_user.auto_gen_auth()
|
||||
else:
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read().strip().decode('utf-8')
|
||||
public_key = ssh_pubkey_gen(private_key=private_key)
|
||||
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
||||
return system_user
|
||||
|
||||
def clean_private_key_file(self):
|
||||
if self.cleaned_data.get('private_key_file'):
|
||||
key_string = self.cleaned_data['private_key_file'].read()
|
||||
self.cleaned_data['private_key_file'].seek(0)
|
||||
if not validate_ssh_private_key(key_string):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return self.cleaned_data['private_key_file']
|
||||
|
||||
def clean_password(self):
|
||||
if not self.cleaned_data.get('password') and \
|
||||
not self.cleaned_data.get('private_key_file') and \
|
||||
not self.cleaned_data.get('auto_generate_key'):
|
||||
raise forms.ValidationError(_('Auth info required, private_key or password'))
|
||||
return self.cleaned_data['password']
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||
'comment', 'shell', 'cluster', 'priority',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
'cluster': forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _(' Select clusters')
|
||||
}
|
||||
),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
'cluster': _('If auto push checked, system user will be create at cluster assets'),
|
||||
'auto_push': _('Auto push system user to asset'),
|
||||
'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'),
|
||||
}
|
||||
|
||||
|
||||
class SystemUserUpdateForm(SystemUserForm):
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
password = self.cleaned_data.get('password', None)
|
||||
private_key_file = self.cleaned_data.get('private_key_file')
|
||||
system_user = super(forms.ModelForm, self).save()
|
||||
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read().strip().decode('utf-8')
|
||||
public_key = ssh_pubkey_gen(private_key=private_key)
|
||||
else:
|
||||
private_key = public_key = None
|
||||
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
||||
return system_user
|
||||
|
||||
def clean_password(self):
|
||||
return self.cleaned_data['password']
|
||||
|
||||
|
||||
class SystemUserAuthForm(forms.Form):
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=False, max_length=128, strip=True)
|
||||
private_key_file = forms.FileField(required=False)
|
||||
|
||||
def clean_private_key_file(self):
|
||||
if self.cleaned_data.get('private_key_file'):
|
||||
key_string = self.cleaned_data['private_key_file'].read()
|
||||
self.cleaned_data['private_key_file'].seek(0)
|
||||
if not validate_ssh_private_key(key_string):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return self.cleaned_data['private_key_file']
|
||||
|
||||
def clean_password(self):
|
||||
if not self.cleaned_data.get('password') and \
|
||||
not self.cleaned_data.get('private_key_file'):
|
||||
msg = _('Auth info required, private_key or password')
|
||||
raise forms.ValidationError(msg)
|
||||
return self.cleaned_data['password']
|
||||
|
||||
def update(self, system_user):
|
||||
password = self.cleaned_data.get('password')
|
||||
private_key_file = self.cleaned_data.get('private_key_file')
|
||||
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read().strip()
|
||||
public_key = ssh_pubkey_gen(private_key=private_key)
|
||||
else:
|
||||
private_key = None
|
||||
public_key = None
|
||||
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
||||
return system_user
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField()
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .asset import *
|
||||
from .label import *
|
||||
from .user import *
|
|
@ -0,0 +1,133 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
from common.utils import get_logger
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
|
||||
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Nodes')
|
||||
}),
|
||||
'admin_user': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Admin user')
|
||||
}),
|
||||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Labels')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
'port': '* required',
|
||||
'admin_user': _('Admin user is a privilege user exist on this asset,'
|
||||
'Example: root or other NOPASSWD sudo privilege user'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class AssetUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Nodes')
|
||||
}),
|
||||
'admin_user': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Admin user')
|
||||
}),
|
||||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Labels')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
'port': '* required',
|
||||
'cluster': '* required',
|
||||
'admin_user': _(
|
||||
'Admin user is a privilege user exist on this asset,'
|
||||
'Example: root or other NOPASSWD sudo privilege user'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
required=True, help_text='* required',
|
||||
label=_('Select assets'), queryset=Asset.objects.all(),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Select assets')
|
||||
}
|
||||
)
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||
)
|
||||
admin_user = forms.ModelChoiceField(
|
||||
required=False, queryset=AdminUser.objects.all(),
|
||||
label=_("Admin user"),
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Admin user')
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'admin_user', 'labels', 'nodes',
|
||||
]
|
||||
widgets = {
|
||||
'labels': forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select labels')}
|
||||
),
|
||||
'nodes': forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select nodes')}
|
||||
),
|
||||
}
|
||||
|
||||
def save(self, commit=True):
|
||||
changed_fields = []
|
||||
for field in self._meta.fields:
|
||||
if self.data.get(field) not in [None, '']:
|
||||
changed_fields.append(field)
|
||||
|
||||
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
||||
if k in changed_fields}
|
||||
assets = cleaned_data.pop('assets')
|
||||
labels = cleaned_data.pop('labels', [])
|
||||
nodes = cleaned_data.pop('nodes')
|
||||
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
|
||||
assets.update(**cleaned_data)
|
||||
|
||||
if labels:
|
||||
for label in labels:
|
||||
label.assets.add(*tuple(assets))
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.assets.add(*tuple(assets))
|
||||
return assets
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import Label, Asset
|
||||
|
||||
__all__ = ['LabelForm']
|
||||
|
||||
|
||||
class LabelForm(forms.ModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
queryset=Asset.objects.all(), label=_('Asset'), required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = ['name', 'value', 'assets']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('instance', None):
|
||||
initial = kwargs.get('initial', {})
|
||||
initial['assets'] = kwargs['instance'].assets.all()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
label = super().save(commit=commit)
|
||||
assets = self.cleaned_data['assets']
|
||||
label.assets.set(assets)
|
||||
return label
|
|
@ -0,0 +1,135 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import AdminUser, SystemUser
|
||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'FileForm', 'SystemUserForm', 'AdminUserForm',
|
||||
]
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField()
|
||||
|
||||
|
||||
class PasswordAndKeyAuthForm(forms.ModelForm):
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(
|
||||
widget=forms.PasswordInput, max_length=128,
|
||||
strip=True, required=False,
|
||||
help_text=_('Password or private key passphrase'),
|
||||
label=_("Password"),
|
||||
)
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False, label=_("Private key"))
|
||||
|
||||
def clean_private_key_file(self):
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
password = self.cleaned_data['password']
|
||||
|
||||
if private_key_file:
|
||||
key_string = private_key_file.read()
|
||||
private_key_file.seek(0)
|
||||
if not validate_ssh_private_key(key_string, password):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return private_key_file
|
||||
|
||||
def validate_password_key(self):
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data.get('private_key_file', '')
|
||||
|
||||
if not password and not private_key_file:
|
||||
raise forms.ValidationError(_(
|
||||
'Password and private key file must be input one'
|
||||
))
|
||||
|
||||
def gen_keys(self):
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
public_key = private_key = None
|
||||
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read().strip().decode('utf-8')
|
||||
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
|
||||
return private_key, public_key
|
||||
|
||||
|
||||
class AdminUserForm(PasswordAndKeyAuthForm):
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
admin_user = super().save(commit=commit)
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
private_key, public_key = super().gen_keys()
|
||||
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
|
||||
return admin_user
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if not self.instance:
|
||||
super().validate_password_key()
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = ['name', 'username', 'password', 'private_key_file', 'comment']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
}
|
||||
|
||||
|
||||
class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
# Admin user assets define, let user select, save it in form not in view
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super().save()
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
|
||||
private_key, public_key = super().gen_keys()
|
||||
|
||||
if auto_generate_key:
|
||||
logger.info('Auto generate key and set system user auth')
|
||||
system_user.auto_gen_auth()
|
||||
else:
|
||||
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
||||
return system_user
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
auto_generate = self.cleaned_data.get('auto_generate_key')
|
||||
if not self.instance and not auto_generate:
|
||||
super().validate_password_key()
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||
'comment', 'shell', 'nodes', 'priority',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
'nodes': forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Nodes')
|
||||
}
|
||||
),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
'nodes': _('If auto push checked, system user will be create at node assets'),
|
||||
'auto_push': _('Auto push system user to asset'),
|
||||
'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'),
|
||||
}
|
|
@ -6,13 +6,12 @@
|
|||
|
||||
Other module of this app shouldn't connect with other app.
|
||||
|
||||
:copyright: (c) 2014-2017 by Jumpserver Team.
|
||||
:copyright: (c) 2014-2018 by Jumpserver Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
from perms.utils import get_user_granted_assets
|
||||
from perms.tasks import push_users
|
||||
from perms.utils import NodePermissionUtil
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .user import AdminUser, SystemUser
|
||||
from .label import Label
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .node import *
|
||||
from .asset import *
|
||||
from .utils import *
|
||||
|
|
|
@ -28,46 +28,36 @@ def default_cluster():
|
|||
return cluster.id
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
# Todo: Move them to settings
|
||||
STATUS_CHOICES = (
|
||||
('In use', _('In use')),
|
||||
('Out of use', _('Out of use')),
|
||||
)
|
||||
TYPE_CHOICES = (
|
||||
('Server', _('Server')),
|
||||
('VM', _('VM')),
|
||||
('Switch', _('Switch')),
|
||||
('Router', _('Router')),
|
||||
('Firewall', _('Firewall')),
|
||||
('Storage', _("Storage")),
|
||||
)
|
||||
ENV_CHOICES = (
|
||||
('Prod', _('Production')),
|
||||
('Dev', _('Development')),
|
||||
('Test', _('Testing')),
|
||||
)
|
||||
def default_node():
|
||||
try:
|
||||
from .node import Node
|
||||
return Node.root()
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
|
||||
cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster'))
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),)
|
||||
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),)
|
||||
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True, default='In use', verbose_name=_('Asset status'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user"))
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
|
||||
|
||||
# Some information
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote control card IP'))
|
||||
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
|
||||
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
|
@ -82,12 +72,13 @@ class Asset(models.Model):
|
|||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||
|
||||
platform = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Platform'))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||
|
||||
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
@ -104,6 +95,12 @@ class Asset(models.Model):
|
|||
return True, ''
|
||||
return False, warning
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows", "Other"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
|
@ -116,36 +113,20 @@ class Asset(models.Model):
|
|||
|
||||
@property
|
||||
def is_connective(self):
|
||||
if not self.is_unixlike():
|
||||
return True
|
||||
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
||||
if val == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def admin_user_avail(self):
|
||||
if self.admin_user:
|
||||
admin_user = self.admin_user
|
||||
elif self.cluster and self.cluster.admin_user:
|
||||
admin_user = self.cluster.admin_user
|
||||
else:
|
||||
return None
|
||||
return admin_user
|
||||
|
||||
@property
|
||||
def is_has_private_admin_user(self):
|
||||
if self.admin_user:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
'groups': [group.name for group in self.groups.all()],
|
||||
}
|
||||
|
||||
def _to_secret_json(self):
|
||||
|
@ -156,13 +137,14 @@ class Asset(models.Model):
|
|||
Todo: May be move to ops implements it
|
||||
"""
|
||||
data = self.to_json()
|
||||
if self.admin_user_avail:
|
||||
admin_user = self.admin_user_avail
|
||||
if self.admin_user:
|
||||
admin_user = self.admin_user
|
||||
data.update({
|
||||
'username': admin_user.username,
|
||||
'password': admin_user.password,
|
||||
'private_key': admin_user.private_key_file,
|
||||
'become': admin_user.become_info,
|
||||
'groups': [node.value for node in self.nodes.all()],
|
||||
})
|
||||
return data
|
||||
|
||||
|
@ -181,7 +163,6 @@ class Asset(models.Model):
|
|||
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
|
||||
hostname=forgery_py.internet.user_name(True),
|
||||
admin_user=choice(AdminUser.objects.all()),
|
||||
cluster=choice(Cluster.objects.all()),
|
||||
port=22,
|
||||
created_by='Fake')
|
||||
try:
|
||||
|
|
|
@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
|
|||
class AssetGroup(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
||||
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class Label(models.Model):
|
||||
SYSTEM_CATEGORY = "S"
|
||||
USER_CATEGORY = "U"
|
||||
CATEGORY_CHOICES = (
|
||||
("S", _("System")),
|
||||
("U", _("User"))
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(
|
||||
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_queryset_group_by_name(cls):
|
||||
names = cls.objects.values_list('name', flat=True)
|
||||
for name in names:
|
||||
yield name, cls.objects.filter(name=name)
|
||||
|
||||
def __str__(self):
|
||||
return "{}:{}".format(self.name, self.value)
|
||||
|
||||
class Meta:
|
||||
db_table = "assets_label"
|
||||
unique_together = ('name', 'value')
|
|
@ -0,0 +1,119 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = ['Node']
|
||||
|
||||
|
||||
class Node(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, unique=True, verbose_name=_("Value"))
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
if self == self.__class__.root():
|
||||
return self.value
|
||||
else:
|
||||
return '{}/{}'.format(self.value, self.parent.full_value)
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
return len(self.key.split(':'))
|
||||
|
||||
def get_next_child_key(self):
|
||||
mark = self.child_mark
|
||||
self.child_mark += 1
|
||||
self.save()
|
||||
return "{}:{}".format(self.key, mark)
|
||||
|
||||
def create_child(self, value):
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(key=child_key, value=value)
|
||||
return child
|
||||
|
||||
def get_children(self):
|
||||
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
|
||||
|
||||
def get_all_children(self):
|
||||
return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
|
||||
|
||||
def get_family(self):
|
||||
children = list(self.get_all_children())
|
||||
children.append(self)
|
||||
return children
|
||||
|
||||
def get_assets(self):
|
||||
from .asset import Asset
|
||||
assets = Asset.objects.filter(nodes__id=self.id)
|
||||
return assets
|
||||
|
||||
def get_active_assets(self):
|
||||
return self.get_assets().filter(is_active=True)
|
||||
|
||||
def get_all_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
assets = Asset.objects.all()
|
||||
else:
|
||||
nodes = self.get_family()
|
||||
assets = Asset.objects.filter(nodes__in=nodes)
|
||||
return assets
|
||||
|
||||
def get_all_active_assets(self):
|
||||
return self.get_all_assets().filter(is_active=True)
|
||||
|
||||
def is_root(self):
|
||||
return self.key == '0'
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.key == "0":
|
||||
return self.__class__.root()
|
||||
elif not self.key.startswith("0"):
|
||||
return self.__class__.root()
|
||||
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
try:
|
||||
parent = self.__class__.objects.get(key=parent_key)
|
||||
except Node.DoesNotExist:
|
||||
return self.__class__.root()
|
||||
else:
|
||||
return parent
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
self.key = parent.get_next_child_key()
|
||||
|
||||
@property
|
||||
def ancestor(self):
|
||||
if self.parent == self.__class__.root():
|
||||
return [self.__class__.root()]
|
||||
else:
|
||||
return [self.parent, *tuple(self.parent.ancestor)]
|
||||
|
||||
@property
|
||||
def ancestor_with_node(self):
|
||||
ancestor = self.ancestor
|
||||
ancestor.insert(0, self)
|
||||
return ancestor
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
obj, created = cls.objects.get_or_create(
|
||||
key='0', defaults={"key": '0', 'value': "ROOT"}
|
||||
)
|
||||
return obj
|
|
@ -26,14 +26,14 @@ signer = get_signer()
|
|||
class AssetUser(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=16, verbose_name=_('Username'))
|
||||
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
|
@ -175,15 +175,12 @@ class AdminUser(AssetUser):
|
|||
return info
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = []
|
||||
for cluster in self.cluster_set.all():
|
||||
assets.extend(cluster.assets.all())
|
||||
assets.extend(self.asset_set.all())
|
||||
return list(set(assets))
|
||||
assets = self.asset_set.all()
|
||||
return assets
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return len(self.get_related_assets())
|
||||
return self.get_related_assets().count()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
@ -212,11 +209,13 @@ class AdminUser(AssetUser):
|
|||
|
||||
class SystemUser(AssetUser):
|
||||
SSH_PROTOCOL = 'ssh'
|
||||
RDP_PROTOCOL = 'rdp'
|
||||
PROTOCOL_CHOICES = (
|
||||
(SSH_PROTOCOL, 'ssh'),
|
||||
(RDP_PROTOCOL, 'rdp'),
|
||||
)
|
||||
|
||||
cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster"))
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
|
||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
|
@ -226,21 +225,6 @@ class SystemUser(AssetUser):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_clusters_assets(self):
|
||||
from .asset import Asset
|
||||
clusters = self.get_clusters()
|
||||
return Asset.objects.filter(cluster__in=clusters)
|
||||
|
||||
def get_clusters(self):
|
||||
return self.cluster.all()
|
||||
|
||||
def get_clusters_joined(self):
|
||||
return ', '.join([cluster.name for cluster in self.get_clusters()])
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return len(self.get_clusters_assets())
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
|
@ -251,6 +235,13 @@ class SystemUser(AssetUser):
|
|||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
@property
|
||||
def assets(self):
|
||||
assets = set()
|
||||
for node in self.nodes.all():
|
||||
assets.update(set(node.get_all_assets()))
|
||||
return assets
|
||||
|
||||
@property
|
||||
def assets_connective(self):
|
||||
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
|
||||
|
@ -264,6 +255,12 @@ class SystemUser(AssetUser):
|
|||
def reachable_assets(self):
|
||||
return self.assets_connective.get('contacted', [])
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
verbose_name = _("System user")
|
||||
|
|
|
@ -1,286 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.core.cache import cache
|
||||
from rest_framework import serializers
|
||||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser
|
||||
from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY
|
||||
|
||||
|
||||
class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
资产组序列化数据模型
|
||||
"""
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = ['id', 'name', 'comment', 'assets_amount', 'assets']
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.asset_count
|
||||
|
||||
|
||||
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
资产更新其系统用户请求的数据结构定义
|
||||
"""
|
||||
system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'system_users']
|
||||
|
||||
|
||||
class GroupUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
资产组更新需要的数据结构
|
||||
"""
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
fields = ['id', 'assets']
|
||||
|
||||
|
||||
class ClusterUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
集群更新资产数据结构
|
||||
"""
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = ['id', 'assets']
|
||||
|
||||
|
||||
class AdminUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
管理用户
|
||||
"""
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
unreachable_amount = serializers.SerializerMethodField()
|
||||
reachable_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('dark'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('contacted'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets_amount
|
||||
|
||||
|
||||
class SystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
系统用户
|
||||
"""
|
||||
unreachable_amount = serializers.SerializerMethodField()
|
||||
reachable_amount = serializers.SerializerMethodField()
|
||||
unreachable_assets = serializers.SerializerMethodField()
|
||||
reachable_assets = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
exclude = ('_password', '_private_key', '_public_key')
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_assets(obj):
|
||||
return obj.unreachable_assets
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_assets(obj):
|
||||
return obj.reachable_assets
|
||||
|
||||
def get_unreachable_amount(self, obj):
|
||||
return len(self.get_unreachable_assets(obj))
|
||||
|
||||
def get_reachable_amount(self, obj):
|
||||
return len(self.get_reachable_assets(obj))
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
amount = 0
|
||||
for cluster in obj.cluster.all():
|
||||
amount += cluster.assets.all().count()
|
||||
return amount
|
||||
|
||||
|
||||
class AdminUserUpdateClusterSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
管理用户更新关联到的集群
|
||||
"""
|
||||
clusters = serializers.PrimaryKeyRelatedField(many=True, queryset=Cluster.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = ['id', 'clusters']
|
||||
|
||||
|
||||
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少
|
||||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
|
||||
|
||||
|
||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
系统用户最基本信息的数据结构
|
||||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username')
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
cluster_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
validators = [] # If not set to [], partial bulk update will be error
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'get_type_display', 'get_env_display',
|
||||
'hardware_info', 'is_connective',
|
||||
])
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
def get_cluster_name(obj):
|
||||
return obj.cluster.name
|
||||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
被授权资产的数据结构
|
||||
"""
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
is_inherited = serializers.SerializerMethodField()
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_inherited", "is_active", "system_users_join", "os",
|
||||
"platform", "comment"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_is_inherited(obj):
|
||||
if getattr(obj, 'inherited', ''):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_system_users_join(obj):
|
||||
return ', '.join([system_user.username for system_user in obj.system_users_granted])
|
||||
|
||||
|
||||
class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
"""
|
||||
普通用户获取授权的资产定义的数据结构
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_inherited", "is_active", "system_users_join",
|
||||
"os", "platform", "comment",
|
||||
)
|
||||
|
||||
|
||||
class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
cluster
|
||||
"""
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
admin_user_name = serializers.SerializerMethodField()
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
system_users = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
@staticmethod
|
||||
def get_admin_user_name(obj):
|
||||
try:
|
||||
return obj.admin_user.name
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def get_system_users(obj):
|
||||
return ', '.join(obj.name for obj in obj.systemuser_set.all())
|
||||
|
||||
|
||||
class AssetGroupGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
授权资产组
|
||||
"""
|
||||
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.assets_granted)
|
||||
|
||||
|
||||
class MyAssetGroupGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
普通用户授权资产组结构
|
||||
"""
|
||||
assets_granted = MyAssetGrantedSerializer(many=True, read_only=True)
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.assets_granted)
|
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .asset import *
|
||||
from .admin_user import *
|
||||
from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from rest_framework import serializers
|
||||
from ..models import Node, AdminUser
|
||||
from ..const import ADMIN_USER_CONN_CACHE_KEY
|
||||
|
||||
|
||||
class AdminUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
管理用户
|
||||
"""
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
unreachable_amount = serializers.SerializerMethodField()
|
||||
reachable_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('dark'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('contacted'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets_amount
|
||||
|
||||
|
||||
class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
管理用户更新关联到的集群
|
||||
"""
|
||||
nodes = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset=Node.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = ['id', 'nodes']
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import Asset
|
||||
from .system_user import AssetSystemUserSerializer
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
validators = [] # If not set to [], partial bulk update will be error
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'hardware_info', 'is_connective',
|
||||
])
|
||||
return fields
|
||||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
被授权资产的数据结构
|
||||
"""
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_active", "system_users_join", "os",
|
||||
"platform", "comment"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_system_users_join(obj):
|
||||
system_users = [s.username for s in obj.system_users_granted]
|
||||
return ', '.join(system_users)
|
||||
|
||||
|
||||
class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
"""
|
||||
普通用户获取授权的资产定义的数据结构
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_active", "system_users_join",
|
||||
"os", "platform", "comment",
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import Asset, Cluster
|
||||
|
||||
|
||||
class ClusterUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
集群更新资产数据结构
|
||||
"""
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = ['id', 'assets']
|
||||
|
||||
|
||||
class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
cluster
|
||||
"""
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
admin_user_name = serializers.SerializerMethodField()
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
system_users = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
@staticmethod
|
||||
def get_admin_user_name(obj):
|
||||
try:
|
||||
return obj.admin_user.name
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def get_system_users(obj):
|
||||
return ', '.join(obj.name for obj in obj.systemuser_set.all())
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from ..models import Label
|
||||
|
||||
|
||||
class LabelSerializer(serializers.ModelSerializer):
|
||||
asset_count = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = '__all__'
|
||||
list_serializer_class = BulkListSerializer
|
||||
|
||||
@staticmethod
|
||||
def get_asset_count(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend(['get_category_display'])
|
||||
return fields
|
||||
|
||||
|
||||
class LabelDistinctSerializer(serializers.ModelSerializer):
|
||||
value = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = ("name", "value")
|
||||
|
||||
@staticmethod
|
||||
def get_value(obj):
|
||||
labels = Label.objects.filter(name=obj["name"])
|
||||
return ', '.join([label.value for label in labels])
|
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from rest_framework import serializers
|
||||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import Asset, Node
|
||||
from .asset import AssetGrantedSerializer
|
||||
|
||||
|
||||
class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
授权资产组
|
||||
"""
|
||||
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
parent = serializers.SerializerMethodField()
|
||||
name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'name', 'value', 'parent',
|
||||
'assets_granted', 'assets_amount',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.assets_granted)
|
||||
|
||||
@staticmethod
|
||||
def get_name(obj):
|
||||
return obj.name
|
||||
|
||||
@staticmethod
|
||||
def get_parent(obj):
|
||||
return obj.parent.id
|
||||
|
||||
|
||||
class NodeSerializer(serializers.ModelSerializer):
|
||||
parent = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount']
|
||||
list_serializer_class = BulkListSerializer
|
||||
|
||||
@staticmethod
|
||||
def get_parent(obj):
|
||||
return obj.parent.id
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.get_all_assets().count()
|
||||
|
||||
def get_fields(self):
|
||||
fields = super().get_fields()
|
||||
field = fields["key"]
|
||||
field.required = False
|
||||
return fields
|
||||
|
||||
|
||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['assets']
|
||||
|
||||
|
||||
class NodeAddChildrenSerializer(serializers.Serializer):
|
||||
nodes = serializers.ListField()
|
|
@ -0,0 +1,54 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from ..models import SystemUser
|
||||
|
||||
|
||||
class SystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
系统用户
|
||||
"""
|
||||
unreachable_amount = serializers.SerializerMethodField()
|
||||
reachable_amount = serializers.SerializerMethodField()
|
||||
unreachable_assets = serializers.SerializerMethodField()
|
||||
reachable_assets = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
exclude = ('_password', '_private_key', '_public_key')
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_assets(obj):
|
||||
return obj.unreachable_assets
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_assets(obj):
|
||||
return obj.reachable_assets
|
||||
|
||||
def get_unreachable_amount(self, obj):
|
||||
return len(self.get_unreachable_assets(obj))
|
||||
|
||||
def get_reachable_amount(self, obj):
|
||||
return len(self.get_reachable_assets(obj))
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.assets)
|
||||
|
||||
|
||||
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少
|
||||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
|
||||
|
||||
|
||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
系统用户最基本信息的数据结构
|
||||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username')
|
|
@ -3,4 +3,3 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
on_app_ready = Signal()
|
||||
on_system_user_auth_changed = Signal(providing_args=['system_user'])
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db.models.signals import post_save, post_init
|
||||
from django.db.models.signals import post_save, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import Asset, SystemUser, Cluster
|
||||
from .models import Asset, SystemUser, Node
|
||||
from .tasks import update_assets_hardware_info_util, \
|
||||
test_asset_connectability_util, \
|
||||
push_system_user_util
|
||||
test_asset_connectability_util, push_system_user_to_node, \
|
||||
push_node_system_users_to_asset
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -25,105 +24,48 @@ def test_asset_conn_on_created(asset):
|
|||
test_asset_connectability_util.delay(asset)
|
||||
|
||||
|
||||
def push_cluster_system_users_to_asset(asset):
|
||||
if asset.cluster:
|
||||
logger.info("Push cluster system user to asset: {}".format(asset))
|
||||
task_name = _("Push cluster system users to asset")
|
||||
system_users = asset.cluster.systemuser_set.all()
|
||||
push_system_user_util.delay(system_users, [asset], task_name)
|
||||
def set_asset_root_node(asset):
|
||||
logger.debug("Set asset default node: {}".format(Node.root()))
|
||||
asset.nodes.add(Node.root())
|
||||
|
||||
|
||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
def on_asset_created(sender, instance=None, created=False, **kwargs):
|
||||
if instance and created:
|
||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
set_asset_root_node(instance)
|
||||
if created:
|
||||
logger.info("Asset `{}` create signal received".format(instance))
|
||||
update_asset_hardware_info_on_created(instance)
|
||||
test_asset_conn_on_created(instance)
|
||||
push_cluster_system_users_to_asset(instance)
|
||||
|
||||
|
||||
@receiver(post_init, sender=Asset)
|
||||
def on_asset_init(sender, instance, created=False, **kwargs):
|
||||
if instance and created is False:
|
||||
instance.__original_cluster = instance.cluster
|
||||
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
||||
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
||||
if instance and not created:
|
||||
for node in instance.nodes.all():
|
||||
push_system_user_to_node(instance, node)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Asset)
|
||||
def on_asset_cluster_changed(sender, instance=None, created=False, **kwargs):
|
||||
if instance and created is False and instance.cluster != instance.__original_cluster:
|
||||
logger.info("Asset cluster changed signal received")
|
||||
push_cluster_system_users_to_asset(instance)
|
||||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
||||
def on_system_user_node_change(sender, instance=None, **kwargs):
|
||||
if instance and kwargs["action"] == "post_add":
|
||||
for pk in kwargs['pk_set']:
|
||||
node = kwargs['model'].objects.get(pk=pk)
|
||||
push_system_user_to_node(instance, node)
|
||||
|
||||
|
||||
def push_to_cluster_assets_on_system_user_created_or_update(system_user):
|
||||
if not system_user.auto_push:
|
||||
return
|
||||
logger.debug("Push system user `{}` to cluster assets".format(system_user.name))
|
||||
for cluster in system_user.cluster.all():
|
||||
task_name = _("Push system user to cluster assets: {}->{}").format(
|
||||
cluster.name, system_user.name
|
||||
)
|
||||
assets = cluster.assets.all()
|
||||
push_system_user_util.delay([system_user], assets, task_name)
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||
if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
|
||||
logger.debug("Asset node change signal received")
|
||||
for pk in kwargs['pk_set']:
|
||||
node = kwargs['model'].objects.get(pk=pk)
|
||||
push_node_system_users_to_asset(node, [instance])
|
||||
|
||||
|
||||
@receiver(post_save, sender=SystemUser)
|
||||
def on_system_user_created_or_updated(sender, instance=None, **kwargs):
|
||||
if instance and instance.auto_push:
|
||||
logger.info("System user `{}` create or update signal received".format(instance))
|
||||
push_to_cluster_assets_on_system_user_created_or_update(instance)
|
||||
|
||||
|
||||
@receiver(post_init, sender=Cluster, dispatch_uid="my_unique_identifier")
|
||||
def on_cluster_init(sender, instance, **kwargs):
|
||||
instance.__original_assets = tuple(instance.assets.values_list('pk', flat=True))
|
||||
instance.__origin_system_users = tuple(instance.systemuser_set.values_list('pk', flat=True))
|
||||
|
||||
|
||||
@receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier")
|
||||
def on_cluster_assets_changed(sender, instance, **kwargs):
|
||||
assets_origin = instance.__original_assets
|
||||
assets_new = instance.assets.values_list('pk', flat=True)
|
||||
assets_added = set(assets_new) - set(assets_origin)
|
||||
if assets_added:
|
||||
logger.debug("Receive cluster change assets signal")
|
||||
logger.debug("Push cluster `{}` system users to: {}".format(
|
||||
instance, ', '.join([str(asset) for asset in assets_added])
|
||||
))
|
||||
assets = []
|
||||
for asset_id in assets_added:
|
||||
try:
|
||||
asset = Asset.objects.get(pk=asset_id)
|
||||
except Asset.DoesNotExist:
|
||||
continue
|
||||
else:
|
||||
assets.append(asset)
|
||||
system_users = [s for s in instance.systemuser_set.all() if s.auto_push]
|
||||
task_name = _("Push system user to assets")
|
||||
push_system_user_util.delay(system_users, assets, task_name)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier2")
|
||||
def on_cluster_system_user_changed(sender, instance, **kwargs):
|
||||
system_users_origin = instance.__origin_system_users
|
||||
system_user_new = instance.systemuser_set.values_list('pk', flat=True)
|
||||
system_users_added = set(system_user_new) - set(system_users_origin)
|
||||
if system_users_added:
|
||||
logger.debug("Receive cluster change system users signal")
|
||||
system_users = []
|
||||
for system_user_id in system_users_added:
|
||||
try:
|
||||
system_user = SystemUser.objects.get(pk=system_user_id)
|
||||
except SystemUser.DoesNotExist:
|
||||
continue
|
||||
else:
|
||||
system_users.append(system_user)
|
||||
logger.debug("Push new system users `{}` to cluster `{}` assets".format(
|
||||
','.join([s.name for s in system_users]), instance
|
||||
))
|
||||
task_name = _(
|
||||
"Push system user to cluster assets: {}->{}").format(
|
||||
instance.name, ', '.join(s.name for s in system_users)
|
||||
)
|
||||
push_system_user_util.delay(system_users, instance.assets.all(), task_name)
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||
if isinstance(instance, Node) and kwargs['action'] == 'post_add':
|
||||
logger.debug("Node assets change signal received")
|
||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
push_node_system_users_to_asset(instance, assets)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ FORKS = 10
|
|||
TIMEOUT = 60
|
||||
logger = get_logger(__file__)
|
||||
CACHE_MAX_TIME = 60*60*60
|
||||
disk_pattern = re.compile(r'^hd|sd|xvd')
|
||||
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
||||
|
||||
|
||||
@shared_task
|
||||
|
@ -36,39 +36,40 @@ def set_assets_hardware_info(result, **kwargs):
|
|||
result_raw = result[0]
|
||||
assets_updated = []
|
||||
for hostname, info in result_raw.get('ok', {}).items():
|
||||
if info:
|
||||
info = info['setup']['ansible_facts']
|
||||
else:
|
||||
info = info.get('setup', {}).get('ansible_facts', {})
|
||||
if not info:
|
||||
logger.error("Get asset info failed: {}".format(hostname))
|
||||
continue
|
||||
|
||||
asset = get_object_or_none(Asset, hostname=hostname)
|
||||
if not asset:
|
||||
continue
|
||||
|
||||
___vendor = info['ansible_system_vendor']
|
||||
___model = info['ansible_product_version']
|
||||
___sn = info['ansible_product_serial']
|
||||
___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['ansible_processor']:
|
||||
if ___cpu_model.endswith('GHz'):
|
||||
for ___cpu_model in info.get('ansible_processor', []):
|
||||
if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
|
||||
break
|
||||
else:
|
||||
___cpu_model = 'Unknown'
|
||||
___cpu_count = info['ansible_processor_count']
|
||||
___cpu_cores = info['ansible_processor_cores']
|
||||
___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb']))
|
||||
___cpu_model = ___cpu_model[:64]
|
||||
___cpu_count = info.get('ansible_processor_count', 0)
|
||||
___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', []))
|
||||
___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb')))
|
||||
disk_info = {}
|
||||
for dev, dev_info in info['ansible_devices'].items():
|
||||
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 = '%s %s' % sum_capacity(disk_info.values())
|
||||
___disk_info = json.dumps(disk_info)
|
||||
|
||||
___platform = info['ansible_system']
|
||||
___os = info['ansible_distribution']
|
||||
___os_version = info['ansible_distribution_version']
|
||||
___os_arch = info['ansible_architecture']
|
||||
___hostname_raw = info['ansible_hostname']
|
||||
___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('___'):
|
||||
|
@ -90,7 +91,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
|||
if task_name is None:
|
||||
task_name = _("Update some assets hardware info")
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
hostname_list = [asset.hostname for asset in assets]
|
||||
hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
|
@ -98,7 +99,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
|||
result = task.run()
|
||||
# Todo: may be somewhere using
|
||||
# Manual run callback function
|
||||
assets_updated = set_assets_hardware_info(result)
|
||||
set_assets_hardware_info(result)
|
||||
return result
|
||||
|
||||
|
||||
|
@ -119,7 +120,10 @@ def update_assets_hardware_info_period():
|
|||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
task_name = _("Update assets hardware info period")
|
||||
hostname_list = [asset.hostname for asset in Asset.objects.all()]
|
||||
hostname_list = [
|
||||
asset.hostname for asset in Asset.objects.all()
|
||||
if asset.is_active and asset.is_unixlike()
|
||||
]
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
|
||||
# Only create, schedule by celery beat
|
||||
|
@ -164,7 +168,10 @@ def test_admin_user_connectability_util(admin_user, task_name):
|
|||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
assets = admin_user.get_related_assets()
|
||||
hosts = [asset.hostname for asset in assets]
|
||||
hosts = [asset.hostname for asset in assets
|
||||
if asset.is_active and asset.is_unixlike()]
|
||||
if not hosts:
|
||||
return
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
|
@ -183,19 +190,10 @@ def test_admin_user_connectability_period():
|
|||
"""
|
||||
A period task that update the ansible task period
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
admin_users = AdminUser.objects.all()
|
||||
for admin_user in admin_users:
|
||||
task_name = _("Test admin user connectability period: {}").format(admin_user)
|
||||
assets = admin_user.get_related_assets()
|
||||
hosts = [asset.hostname for asset in assets]
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
interval=3600, is_periodic=True,
|
||||
callback=set_admin_user_connectability_info.name,
|
||||
)
|
||||
task_name = _("Test admin user connectability period: {}".format(admin_user.name))
|
||||
test_admin_user_connectability_util(admin_user, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
|
@ -262,8 +260,8 @@ def test_system_user_connectability_util(system_user, task_name):
|
|||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
assets = system_user.get_clusters_assets()
|
||||
hosts = [asset.hostname for asset in assets]
|
||||
assets = system_user.assets
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
if not hosts:
|
||||
logger.info("No hosts, passed")
|
||||
|
@ -289,21 +287,10 @@ def test_system_user_connectability_manual(system_user):
|
|||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
def test_system_user_connectability_period():
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
system_users = SystemUser.objects.all()
|
||||
for system_user in system_users:
|
||||
task_name = _("Test system user connectability period: {}").format(
|
||||
system_user.name
|
||||
)
|
||||
assets = system_user.get_clusters_assets()
|
||||
hosts = [asset.hostname for asset in assets]
|
||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=False, run_as=system_user.name,
|
||||
created_by='System', interval=3600, is_periodic=True,
|
||||
callback=set_admin_user_connectability_info.name,
|
||||
)
|
||||
task_name = _("test system user connectability period: {}".format(system_user))
|
||||
test_system_user_connectability_util(system_user, task_name)
|
||||
|
||||
|
||||
#### Push system user tasks ####
|
||||
|
@ -313,8 +300,9 @@ def get_push_system_user_tasks(system_user):
|
|||
if system_user.username == "root":
|
||||
return []
|
||||
|
||||
tasks = [
|
||||
{
|
||||
tasks = []
|
||||
if system_user.password:
|
||||
tasks.append({
|
||||
'name': 'Add user {}'.format(system_user.username),
|
||||
'action': {
|
||||
'module': 'user',
|
||||
|
@ -323,8 +311,9 @@ def get_push_system_user_tasks(system_user):
|
|||
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',
|
||||
|
@ -332,8 +321,9 @@ def get_push_system_user_tasks(system_user):
|
|||
system_user.username, system_user.public_key
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
})
|
||||
if system_user.sudo:
|
||||
tasks.append({
|
||||
'name': 'Set {} sudo setting'.format(system_user.username),
|
||||
'action': {
|
||||
'module': 'lineinfile',
|
||||
|
@ -344,8 +334,7 @@ def get_push_system_user_tasks(system_user):
|
|||
system_user.sudo,
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
return tasks
|
||||
|
||||
|
||||
|
@ -354,13 +343,14 @@ def push_system_user_util(system_users, assets, task_name):
|
|||
from ops.utils import update_or_create_ansible_task
|
||||
tasks = []
|
||||
for system_user in system_users:
|
||||
if system_user.is_need_push():
|
||||
tasks.extend(get_push_system_user_tasks(system_user))
|
||||
|
||||
if not tasks:
|
||||
logger.info("Not tasks, passed")
|
||||
return {}
|
||||
|
||||
hosts = [asset.hostname for asset in assets]
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
if not hosts:
|
||||
logger.info("Not hosts, passed")
|
||||
return {}
|
||||
|
@ -371,35 +361,47 @@ def push_system_user_util(system_users, assets, task_name):
|
|||
return task.run()
|
||||
|
||||
|
||||
@shared_task
|
||||
def push_system_user_to_cluster_assets_manual(system_user):
|
||||
task_name = _("Push system user to cluster assets: {}").format(system_user.name)
|
||||
assets = system_user.get_clusters_assets()
|
||||
return push_system_user_util([system_user], assets, task_name)
|
||||
def get_node_push_system_user_task_name(system_user, node):
|
||||
return _("Push system user to node: {} => {}").format(
|
||||
system_user.name,
|
||||
node.value
|
||||
)
|
||||
|
||||
|
||||
def push_system_user_to_node(system_user, node):
|
||||
assets = node.get_all_assets()
|
||||
task_name = get_node_push_system_user_task_name(system_user, node)
|
||||
push_system_user_util.delay([system_user], assets, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
def push_system_user_period():
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
clusters = Cluster.objects.all()
|
||||
def push_system_user_related_nodes(system_user):
|
||||
nodes = system_user.nodes.all()
|
||||
for node in nodes:
|
||||
push_system_user_to_node(system_user, node)
|
||||
|
||||
for cluster in clusters:
|
||||
tasks = []
|
||||
system_users = [system_user for system_user in cluster.systemuser_set.all() if system_user.auto_push]
|
||||
if not system_users:
|
||||
return
|
||||
for system_user in system_users:
|
||||
tasks.extend(get_push_system_user_tasks(system_user))
|
||||
|
||||
task_name = _("Push cluster system users to assets period: {}").format(
|
||||
cluster.name
|
||||
)
|
||||
hosts = [asset.hostname for asset in cluster.assets.all()]
|
||||
update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
interval=60*60*24, is_periodic=True,
|
||||
)
|
||||
@shared_task
|
||||
def push_system_user_to_assets_manual(system_user):
|
||||
push_system_user_related_nodes(system_user)
|
||||
|
||||
|
||||
def push_node_system_users_to_asset(node, assets):
|
||||
system_users = []
|
||||
nodes = node.ancestor_with_node
|
||||
# 获取该节点所有父节点有的系统用户, 然后推送
|
||||
for n in nodes:
|
||||
system_users.extend(list(n.systemuser_set.all()))
|
||||
|
||||
if system_users:
|
||||
task_name = _("Push system users to node: {}").format(node.value)
|
||||
push_system_user_util.delay(system_users, assets, task_name)
|
||||
|
||||
|
||||
# @shared_task
|
||||
# @register_as_period_task(interval=3600)
|
||||
# @after_app_ready_start
|
||||
# # @after_app_shutdown_clean
|
||||
# def push_system_user_period():
|
||||
# for system_user in SystemUser.objects.all():
|
||||
# push_system_user_related_nodes(system_user)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
{% load bootstrap3 %}
|
||||
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
|
||||
<form method="post" class="form-horizontal" action="" id="fm_asset_group_bulk_update">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
|
||||
<div class="col-sm-9" id="select2-container">
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_class %}modal-lg{% endblock %}
|
||||
{% block modal_id %}asset_list_modal{% endblock %}
|
||||
{#{% block modal_title%}{% trans "Please select assets" %}{% endblock %}#}
|
||||
{% block modal_body %}
|
||||
{#<div class="btn-group" style="float: right">#}
|
||||
{# <button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>#}
|
||||
{# <ul class="dropdown-menu labels">#}
|
||||
{# {% for label in labels %}#}
|
||||
{# <li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>#}
|
||||
{# {% endfor %}#}
|
||||
{# </ul>#}
|
||||
{#</div>#}
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_modal_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
var modal_table;
|
||||
|
||||
function initModalTable() {
|
||||
var options = {
|
||||
ele: $('#asset_modal_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
$(td).html(rowData.hardware_info)
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData === 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-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_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
modal_table = jumpserver.initServerSideDataTable(options);
|
||||
return modal_table;
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initModalTable();
|
||||
}).on('click', '#btn_select_assets', function () {
|
||||
var data_table = $('#asset_modal_table').DataTable();
|
||||
var id_list = [];
|
||||
data_table.rows({selected: true}).every(function(){
|
||||
id_list.push(this.data().id);
|
||||
});
|
||||
var current_node;
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
var data = {
|
||||
'assets': id_list
|
||||
};
|
||||
|
||||
var success = function () {
|
||||
modal_table.ajax.reload()
|
||||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/add/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_select_assets{% endblock %}
|
||||
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create system user' %}</h5>
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
@ -39,7 +39,6 @@
|
|||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.priority layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.cluster layout="horizontal" %}
|
||||
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
|
@ -52,8 +51,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="auth-fields">
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||
|
@ -82,6 +81,14 @@
|
|||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||
var password_id = '#' + '{{ form.password.id_for_label }}';
|
||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||
|
||||
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
|
||||
|
||||
function authFieldsDisplay() {
|
||||
if ($(auto_generate_key).prop('checked')) {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
|
@ -89,9 +96,23 @@
|
|||
$('.auth-fields').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function protocolChange() {
|
||||
if ($(protocol_id).attr('value') === 'rdp') {
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).addClass('hidden')
|
||||
});
|
||||
$(password_id).removeClass('hidden')
|
||||
} else {
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).removeClass('hidden')
|
||||
});
|
||||
}
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
$(auto_generate_key).change(function () {
|
||||
authFieldsDisplay();
|
||||
});
|
||||
|
|
|
@ -20,14 +20,6 @@
|
|||
<li class="active">
|
||||
<a href="{% url 'assets:admin-user-assets' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Assets list' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-delete-admin-user">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
@ -59,7 +51,6 @@
|
|||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -109,7 +100,7 @@ function initTable() {
|
|||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
|
@ -117,8 +108,9 @@ function initTable() {
|
|||
}
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
|
||||
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "type" }, {data: "is_connective" }],
|
||||
columns: [
|
||||
{data: function(){return ""}}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "port" }, {data: "is_connective" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
|
@ -127,14 +119,6 @@ function initTable() {
|
|||
$(document).ready(function () {
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete-admin-user', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ admin_user.name }}";
|
||||
var uid = "{{ admin_user.id }}";
|
||||
var the_url = '{% url "api-assets:admin-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
var redirect_url = "{% url 'assets:admin-user-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
|
||||
var error = function (data) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create admin user' %}</h5>
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ admin_user.name }}</b></span>
|
||||
|
@ -77,11 +77,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
|
||||
<div class="panel panel-info">
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Using this as cluster admin user' %}
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Replace node assets admin user with this' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit" id="table-clusters">
|
||||
|
@ -89,25 +88,19 @@
|
|||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Select cluster' %}" id="cluster_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for cluster in cluster_remain %}
|
||||
<option value="{{ cluster.id }}" id="opt_{{ cluster.id }}" >{{ cluster.name }}</option>
|
||||
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.value }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-cluster">{% trans 'Confirm' %}</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" id="btn-change-admin-user">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for cluster in admin_user.cluster_set.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_cluster" data-gid={{ cluster.id }}>{{ cluster.name }}</b></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -123,29 +116,20 @@
|
|||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function bindToCluster(clusters) {
|
||||
var the_url = "{% url 'api-assets:admin-user-add-clusters' pk=admin_user.id %}";
|
||||
function replaceNodeAssetsAdminUser(nodes) {
|
||||
var the_url = "{% url 'api-assets:replace-nodes-admin-user' pk=admin_user.id %}";
|
||||
var body = {
|
||||
clusters: clusters
|
||||
nodes: nodes
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#cluster_selected').val('');
|
||||
$.map(jumpserver.cluster_selected, function(cluster_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('#table-clusters tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_cluster" data-gid="' + index + '">' + cluster_name + '</b></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
$.map(jumpserver.cluster_selected, function(value, index) {
|
||||
$('#nodes_selected').val('');
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.cluster_selected = {};
|
||||
jumpserver.nodes_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -154,15 +138,15 @@ function bindToCluster(clusters) {
|
|||
});
|
||||
}
|
||||
|
||||
jumpserver.cluster_selected = {};
|
||||
jumpserver.nodes_selected = {};
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.cluster_selected[data.id] = data.text;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
}).on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.cluster_selected[data.id]
|
||||
delete jumpserver.nodes_selected[data.id]
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-delete-admin-user', function () {
|
||||
|
@ -173,15 +157,15 @@ $(document).ready(function () {
|
|||
var redirect_url = "{% url 'assets:admin-user-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
.on('click', '#btn-add-cluster', function () {
|
||||
if (Object.keys(jumpserver.cluster_selected).length === 0) {
|
||||
.on('click', '#btn-change-admin-user', function () {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var clusters = [];
|
||||
$.map(jumpserver.cluster_selected, function(value, index) {
|
||||
clusters.push(index);
|
||||
var nodes = [];
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
nodes.push(index);
|
||||
});
|
||||
bindToCluster(clusters)
|
||||
replaceNodeAssetsAdminUser(nodes);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。可以设置主机级别管理用户,也设置集群级别管理用户,这样资产可以不用再单独设置
|
||||
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -74,7 +74,8 @@ $(document).ready(function(){
|
|||
if (val === 100) {
|
||||
innerHtml = "<span class='text-navy'>" + val + "% </span>";
|
||||
} else {
|
||||
innerHtml = "<span class='text-danger'>" + val + "% </span>";
|
||||
var num = new Number(val);
|
||||
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load asset_tags %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
|
@ -15,25 +17,48 @@
|
|||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.cluster layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
{% bootstrap_field form.env layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Group' %}</h3>
|
||||
{% bootstrap_field form.groups layout="horizontal" %}
|
||||
<h3>{% trans 'Node' %}</h3>
|
||||
{% bootstrap_field form.nodes layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Labels' %}</h3>
|
||||
<div class="form-group {% if form.errors.labels %} has-error {% endif %}">
|
||||
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
|
||||
<div class="col-md-9">
|
||||
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Select labels' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||
<optgroup label="{{ name }}">
|
||||
{% for label in labels %}
|
||||
{% if label in form.labels.initial %}
|
||||
<option value="{{ label.id }}" selected>{{ label.value }}</option>
|
||||
{% else %}
|
||||
<option value="{{ label.id }}">{{ label.value }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if form.errors.labels %}
|
||||
{% for e in form.errors.labels %}
|
||||
<div class="help-block">{{ e }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
|
@ -45,11 +70,20 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
<script>
|
||||
function format(item) {
|
||||
var group = item.element.parentElement.label;
|
||||
return group + ':' + item.text;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
})
|
||||
</script>
|
||||
$(".labels").select2({
|
||||
allowClear: true,
|
||||
templateSelection: format
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -71,27 +71,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
{% if asset.admin_user_avail %}
|
||||
<td><b>{{ asset.admin_user_avail.name }}</b></td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Remote card IP' %}:</td>
|
||||
<td><b>{{ asset.remote_card_ip|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Cluster' %}:</td>
|
||||
<td><b>{{ asset.cluster.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Cabinet number' %}:</td>
|
||||
<td><b>{{ asset.cabinet_no|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Cabinet position' %}:</td>
|
||||
<td><b>{{ asset.cabinet_pos|default:"" }}</b></td>
|
||||
<td><b>{{ asset.admin_user }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Vendor' %}:</td>
|
||||
|
@ -121,22 +101,10 @@
|
|||
<td>{% trans 'OS' %}:</td>
|
||||
<td><b>{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset status' %}:</td>
|
||||
<td><b>{{ asset.status }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Is active' %}:</td>
|
||||
<td><b>{{ asset.is_active|yesno:"Yes,No" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset type' %}:</td>
|
||||
<td><b>{{ asset.type }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset environment' %}:</td>
|
||||
<td><b>{{ asset.env }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Serial number' %}:</td>
|
||||
<td><b>{{ asset.sn|default:"" }}</b></td>
|
||||
|
@ -210,7 +178,7 @@
|
|||
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Asset groups' %}
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit" id="add-asset2group">
|
||||
|
@ -218,25 +186,25 @@
|
|||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Join asset groups' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset_group in asset_groups_remain %}
|
||||
<option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}" >{{ asset_group.name }}</option>
|
||||
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes_remain %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-user-group">{% trans 'Confirm' %}</button>
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-update-nodes">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for asset_group in asset_groups %}
|
||||
{% for node in asset.nodes.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-leave-group" 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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -244,6 +212,19 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Labels' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul class="tag-list" style="padding: 0">
|
||||
{% for label in asset.labels.all %}
|
||||
<li ><a href=""><i class="fa fa-tag"></i> {{ label.name }}:{{ label.value }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -254,28 +235,28 @@
|
|||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.groups_selected = {};
|
||||
function updateAssetGroups(groups) {
|
||||
jumpserver.nodes_selected = {};
|
||||
function updateAssetNodes(nodes) {
|
||||
var the_url = "{% url 'api-assets:asset-detail' pk=asset.id %}";
|
||||
var body = {
|
||||
groups: Object.assign([], groups)
|
||||
nodes: Object.assign([], nodes)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#groups_selected').val('');
|
||||
$.map(jumpserver.groups_selected, function(group_name, index) {
|
||||
$.map(jumpserver.nodes_selected, function(group_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('#add-asset2group tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-group" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'<td><b class="bdg_node" data-gid="' + index + '">' + group_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-node" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.groups_selected = {};
|
||||
jumpserver.nodes_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -304,10 +285,10 @@ function refreshAssetHardware() {
|
|||
$(document).ready(function () {
|
||||
$('.select2.groups').select2().on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.groups_selected[data.id] = data.text;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
}).on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.groups_selected[data.id]
|
||||
delete jumpserver.nodes_selected[data.id]
|
||||
});
|
||||
}).on('click', '#is_active', function () {
|
||||
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
|
||||
|
@ -322,37 +303,37 @@ $(document).ready(function () {
|
|||
body: JSON.stringify(body),
|
||||
success_message: success
|
||||
});
|
||||
if (status == "False") {
|
||||
if (status === "False") {
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
|
||||
}else{
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
|
||||
}
|
||||
}).on('click', '#btn-add-user-group', function () {
|
||||
if (Object.keys(jumpserver.groups_selected).length === 0) {
|
||||
}).on('click', '#btn-update-nodes', function () {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var groups = $('.bdg_group').map(function() {
|
||||
var nodes = $('.bdg_node').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.groups_selected, function(value, index) {
|
||||
groups.push(index);
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
nodes.push(index);
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
updateAssetGroups(groups)
|
||||
}).on('click', '.btn-leave-group', function() {
|
||||
updateAssetNodes(nodes)
|
||||
}).on('click', '.btn-leave-node', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_group');
|
||||
var $badge = $tr.find('.bdg_node');
|
||||
var gid = $badge.data('gid');
|
||||
var group_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var groups = $('.bdg_group').map(function () {
|
||||
var groups = $('.bdg_node').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateAssetGroups(groups)
|
||||
updateAssetNodes(groups)
|
||||
}).on('click', '.btn-delete-asset', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ asset.hostname }}";
|
||||
|
|
|
@ -1,244 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Group assets' %} </a></li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-group-update' pk=asset_group.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-del">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ asset_group.name }} </b><span class="badge"> {{ asset_group.assets.all.count }}</span></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover " id="asset_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Alive' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Add assets to this group' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select assets' %}" class="select2 asset-select" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset in assets_remain %}
|
||||
<option value="{{ asset.id }}"> {{ asset.hostname }} </option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-primary btn-sm btn-add-asset">{% trans 'Add' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.assets_selected = {};
|
||||
function addAssets(assets) {
|
||||
var the_url = "{% url 'api-assets:group-add-assets' pk=asset_group.id %}";
|
||||
var body = {
|
||||
assets: assets
|
||||
};
|
||||
|
||||
var $data_table = $("#asset_list_table").DataTable();
|
||||
var success = function(data) {
|
||||
$('.select2-selection__rendered').empty();
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.groups_selected = {};
|
||||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PUT',
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
function leaveGroup(obj, name, url, data) {
|
||||
var body = data;
|
||||
var success = function() {
|
||||
$(obj).parent().parent().remove();
|
||||
};
|
||||
var fail = function() {
|
||||
console.log("Remove failed")
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH',
|
||||
success: success,
|
||||
error: fail
|
||||
});
|
||||
}
|
||||
|
||||
Array.prototype.remove = function(val) {
|
||||
var index = this.indexOf(val);
|
||||
if (index > -1) {
|
||||
this.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.unique = function(){
|
||||
var res = [];
|
||||
var json = {};
|
||||
for(var i = 0; i < this.length; i++){
|
||||
if(!json[this[i]]){
|
||||
res.push(this[i]);
|
||||
json[this[i]] = 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', rowData.id);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-leave-group" data-aid="{{ DEFAULT_PK }}">{% trans "Remove" %}</a>'.replace('{{ DEFAULT_PK }}', rowData.id);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?asset_group_id={{ asset_group.id }}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
||||
$('.select2.asset-select').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id]
|
||||
});
|
||||
|
||||
initTable();
|
||||
|
||||
})
|
||||
|
||||
.on('click', ".btn-add-asset", function () {
|
||||
if (Object.keys(jumpserver.assets_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var assets_id = [];
|
||||
$.map(jumpserver.assets_selected, function(value, index) {
|
||||
assets_id.push(index);
|
||||
});
|
||||
|
||||
addAssets(assets_id);
|
||||
})
|
||||
|
||||
.on('click', '.btn-leave-group', function () {
|
||||
var $this = $(this);
|
||||
var the_url = "{% url 'api-assets:group-update-assets' pk=asset_group.id %}";
|
||||
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
|
||||
var assets = [];
|
||||
$('#asset_list_table > tbody > tr').map(function () {
|
||||
assets.push($(this).closest("tr").find(":nth-child(1) > a").attr("data-aid"))
|
||||
});
|
||||
var delete_asset_id = $(this).data('aid');
|
||||
assets.remove(delete_asset_id);
|
||||
var data = {"assets": assets};
|
||||
leaveGroup($this, name, the_url, data);
|
||||
}).on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ asset_group.name}}";
|
||||
var uid = "{{ asset_group.id }}";
|
||||
var the_url = '{% url "api-assets:asset-group-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
var redirect_url = "{% url 'assets:asset-group-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,164 +0,0 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url "assets:asset-group-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset group" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_groups_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_group_bulk_update_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#asset_groups_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-group-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_group_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:asset-group-list" %}',
|
||||
columns: [{data: "id"}, {data: "name" }, {data: "assets_amount" }, {data: "comment" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', '.btn_asset_group_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#asset_groups_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:asset-group-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#asset_groups_list_table').DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (id_list === []) {
|
||||
return false;
|
||||
}
|
||||
var the_url = '{% url "api-assets:asset-group-list" %}';
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected groups !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'Group deleted' %}";
|
||||
swal("{% trans 'Group Delete' %}", msg, "success");
|
||||
$('#asset_groups_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Group deleting failed.' %}";
|
||||
swal("{% trans 'Group Delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
function doUpdate() {
|
||||
$('#asset_group_bulk_update_modal').modal('show');
|
||||
}
|
||||
switch(action) {
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
case 'update':
|
||||
doUpdate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
.on('click', '#btn_asset_group_bulk_update', function () {
|
||||
var json_data = $("#fm_asset_group_bulk_update").serializeObject();
|
||||
var body = {};
|
||||
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
|
||||
if (json_data.type != '') {
|
||||
body.type = json_data.type;
|
||||
}
|
||||
|
||||
if (json_data.assets != undefined) {
|
||||
body.assets = json_data.assets;
|
||||
}
|
||||
if (typeof body.assets === 'string') {
|
||||
body.assets = [parseInt(body.assets)]
|
||||
} else if(typeof body.assets === 'array') {
|
||||
var new_assets = body.assets.map(Number);
|
||||
body.assets = new_assets;
|
||||
}
|
||||
|
||||
if (json_data.system_users != undefined) {
|
||||
body.system_users = json_data.system_users;
|
||||
}
|
||||
if (typeof body.system_users === 'string') {
|
||||
body.system_users = [parseInt(body.system_users)];
|
||||
} else if (typeof body.system_users === 'array') {
|
||||
var new_system_users = body.system_users.map(Number);
|
||||
body.system_users = new_system_users;
|
||||
}
|
||||
|
||||
var post_list = [];
|
||||
var $data_table = $('#asset_groups_list_table').DataTable()
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
var content = Object.assign({id: this.data().id}, body);
|
||||
post_list.push(content);
|
||||
});
|
||||
if (post_list === []) {
|
||||
return false
|
||||
}
|
||||
var the_url = '{% url "api-assets:asset-group-list" %}';
|
||||
var success = function() {
|
||||
var msg = "{% trans 'The selected asset groups has been updated successfully.' %}";
|
||||
swal("{% trans 'AssetGroup Updated' %}", msg, "success");
|
||||
$('#asset_groups_list_table').DataTable().ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
};
|
||||
{# console.log(JSON.stringify(post_list));#}
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
|
||||
$('#asset_group_bulk_update_modal').modal('hide');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -1,14 +1,66 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
div#rMenu {
|
||||
position:absolute;
|
||||
visibility:hidden;
|
||||
text-align: left;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
float: left;
|
||||
padding: 5px 0;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
div#rMenu li{
|
||||
margin: 1px 0;
|
||||
cursor: pointer;
|
||||
{#list-style: none outside none;#}
|
||||
}
|
||||
.dropdown a:hover {
|
||||
background-color: #f1f1f1
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
{% block content_left_head %}{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree">
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="tree-toggle">
|
||||
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
|
||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mail-box-header">
|
||||
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||
<div class="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||
|
@ -19,18 +71,20 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
||||
<div class="btn-group" style="float: right">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu labels">
|
||||
{% for label in labels %}
|
||||
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Port' %}</th>
|
||||
<th class="text-center">{% trans 'Cluster' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
|
@ -39,12 +93,13 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="remove">{% trans 'Remove from this node' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active selected' %}</option>
|
||||
</select>
|
||||
|
@ -54,15 +109,31 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rMenu">
|
||||
<ul class="dropdown-menu">
|
||||
<li id="menu_asset_create" class="btn-create-asset" tabindex="-1"><a>{% trans 'Create asset' %}</a></li>
|
||||
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a>{% trans 'Add asset' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a>{% trans 'Add node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a>{% trans 'Rename node' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a>{% trans 'Delete node' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% include 'assets/_asset_import_modal.html' %}
|
||||
{#{% include 'assets/_asset_bulk_update_modal.html' %}#}
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
<script>
|
||||
var zTree, rMenu, asset_table, show = 0;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
|
@ -72,21 +143,18 @@ function initTable() {
|
|||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
$(td).html(rowData.cluster_name)
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
$(td).html(rowData.hardware_info)
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
if (cellData == 'Unknown'){
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData === 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
|
@ -94,7 +162,7 @@ function initTable() {
|
|||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 8, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-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_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
|
@ -102,19 +170,263 @@ function initTable() {
|
|||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "cluster"},
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
return jumpserver.initServerSideDataTable(options);
|
||||
asset_table = jumpserver.initServerSideDataTable(options);
|
||||
return asset_table
|
||||
}
|
||||
|
||||
|
||||
function addTreeNode() {
|
||||
hideRMenu();
|
||||
var parentNode = zTree.getSelectedNodes()[0];
|
||||
if (!parentNode){
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.id );
|
||||
$.post(url, {}, function (data, status){
|
||||
if (status === "success") {
|
||||
var newNode = {
|
||||
name: data["value"],
|
||||
id: data["id"],
|
||||
pId: parentNode.id
|
||||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
|
||||
if (current_node.children && current_node.children.length > 0) {
|
||||
alert("{% trans 'Have child node, cancel' %}")
|
||||
} else {
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id );
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
success: function () {
|
||||
zTree.removeNode(current_node);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function editTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.value) {
|
||||
current_node.name = current_node.value;
|
||||
}
|
||||
zTree.editName(current_node);
|
||||
}
|
||||
|
||||
|
||||
function OnRightClick(event, treeId, treeNode) {
|
||||
if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length === 0) {
|
||||
zTree.cancelSelectedNode();
|
||||
showRMenu("root", event.clientX, event.clientY);
|
||||
} else if (treeNode && !treeNode.noR) {
|
||||
zTree.selectNode(treeNode);
|
||||
showRMenu("node", event.clientX, event.clientY);
|
||||
}
|
||||
}
|
||||
|
||||
function showRMenu(type, x, y) {
|
||||
$("#rMenu ul").show();
|
||||
{#if (type === "root") {#}
|
||||
{# return#}
|
||||
{# } else {#}
|
||||
{# $("#m_del").show();#}
|
||||
{# $("#m_check").show();#}
|
||||
{# $("#m_unCheck").show();#}
|
||||
{# }#}
|
||||
x -= 220;
|
||||
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
||||
|
||||
$("body").bind("mousedown", onBodyMouseDown);
|
||||
}
|
||||
|
||||
function beforeClick(treeId, treeNode, clickFlag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function hideRMenu() {
|
||||
if (rMenu) rMenu.css({"visibility": "hidden"});
|
||||
$("body").unbind("mousedown", onBodyMouseDown);
|
||||
}
|
||||
|
||||
function onBodyMouseDown(event){
|
||||
if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onRename(event, treeId, treeNode, isCancel){
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id);
|
||||
var data = {"value": treeNode.name};
|
||||
if (isCancel){
|
||||
return
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: "PATCH"
|
||||
})
|
||||
}
|
||||
|
||||
function onSelected(event, treeNode) {
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
|
||||
function selectQueryNode() {
|
||||
var query_node_id = $.getUrlParam("node");
|
||||
var cookie_node_id = getCookie('node_selected');
|
||||
var node;
|
||||
var node_id;
|
||||
|
||||
if (query_node_id !== null) {
|
||||
node_id = query_node_id
|
||||
} else if (cookie_node_id !== null) {
|
||||
node_id = cookie_node_id;
|
||||
}
|
||||
|
||||
node = zTree.getNodesByParam("id", node_id, null);
|
||||
if (node){
|
||||
zTree.selectNode(node[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function beforeDrag() {
|
||||
return true
|
||||
}
|
||||
|
||||
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesNames = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesNames.push(value.value);
|
||||
});
|
||||
|
||||
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
|
||||
if (confirm(msg)){
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function onDrag(event, treeId, treeNodes) {
|
||||
}
|
||||
|
||||
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesIds = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesIds.push(value.id);
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
|
||||
var body = {nodes: treeNodesIds};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "PUT",
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
}
|
||||
|
||||
function initTree() {
|
||||
var setting = {
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showLine: true
|
||||
},
|
||||
data: {
|
||||
simpleData: {
|
||||
enable: true
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
enable: true,
|
||||
showRemoveBtn: false,
|
||||
showRenameBtn: false,
|
||||
drag: {
|
||||
isCopy: true,
|
||||
isMove: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onRightClick: OnRightClick,
|
||||
beforeClick: beforeClick,
|
||||
onRename: onRename,
|
||||
onSelected: onSelected,
|
||||
beforeDrag: beforeDrag,
|
||||
onDrag: onDrag,
|
||||
beforeDrop: beforeDrop,
|
||||
onDrop: onDrop
|
||||
}
|
||||
};
|
||||
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
{#if (value["key"] === "0") {#}
|
||||
value["open"] = true;
|
||||
{# }#}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
rMenu = $("#rMenu");
|
||||
selectQueryNode();
|
||||
});
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#split-left").hide(500, function () {
|
||||
$("#split-right").attr("class", "col-lg-12");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||
show = 1;
|
||||
});
|
||||
} else {
|
||||
$("#split-right").attr("class", "col-lg-9");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||
$("#split-left").show(500);
|
||||
show = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
initTree();
|
||||
})
|
||||
.on('click', '.labels li', function () {
|
||||
var val = $(this).text();
|
||||
$("#asset_list_table_filter input").val(val);
|
||||
asset_table.search(val).draw();
|
||||
})
|
||||
.on('click', '.btn_export', function () {
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var rows = $data_table.rows('.selected').data();
|
||||
|
@ -154,7 +466,16 @@ $(document).ready(function(){
|
|||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
})
|
||||
|
||||
.on('click', '.btn-create-asset', function () {
|
||||
var url = "{% url 'assets:asset-create' %}";
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
url += "?node_id=" + current_node.id;
|
||||
}
|
||||
window.open(url, '_self');
|
||||
})
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $("#asset_list_table").DataTable();
|
||||
|
@ -185,7 +506,7 @@ $(document).ready(function(){
|
|||
data.push(obj);
|
||||
});
|
||||
function success() {
|
||||
location.reload()
|
||||
asset_table.ajax.reload()
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -201,7 +522,7 @@ $(document).ready(function(){
|
|||
data.push(obj);
|
||||
});
|
||||
function success() {
|
||||
location.reload();
|
||||
asset_table.ajax.reload()
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -245,6 +566,31 @@ $(document).ready(function(){
|
|||
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
|
||||
location.href = url
|
||||
}
|
||||
|
||||
function doRemove() {
|
||||
var current_node;
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
var data = {
|
||||
'assets': id_list
|
||||
};
|
||||
|
||||
var success = function () {
|
||||
asset_table.ajax.reload()
|
||||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
})
|
||||
}
|
||||
switch(action) {
|
||||
case 'deactive':
|
||||
doDeactive();
|
||||
|
@ -258,10 +604,13 @@ $(document).ready(function(){
|
|||
case 'active':
|
||||
doActive();
|
||||
break;
|
||||
case 'remove':
|
||||
doRemove();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
|
@ -1,124 +0,0 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="myModalLabel">分配/回收资产</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" style="padding-bottom: 0px;">
|
||||
<table aria-describedby="editable_info" role="grid" class="table table-striped table-bordered table-hover dataTable" id="editable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" style="background-color:white">
|
||||
<input type="checkbox" id="check_all" onclick="checkAll()">
|
||||
</th>
|
||||
<th id="th_no">id</th>
|
||||
<th>资产名称</th>
|
||||
<th>IP</th>
|
||||
<th>类型</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset in assets %}
|
||||
{% if asset.id in all_assets %}
|
||||
<tr name="oAssets" class="odd selected text-center">
|
||||
<td class="text-center" ><input type="checkbox" name="checked" value="{{ asset.id }}" checked="checked"></td>
|
||||
{% else %}
|
||||
<tr name="oAssets">
|
||||
<td class="text-center"><input type="checkbox" name="checked" value="{{ asset.id }}" ></td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ asset.id }}</td>
|
||||
<td class="text-center">{{ asset.hostname }}</td>
|
||||
<td class="text-center">{{ asset.ip }}</td>
|
||||
<td class="text-center">{{ asset.env }}-{{ asset.type }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" id="close-btn">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="save-btn">保存</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
var table = $('#editable').DataTable({
|
||||
"aLengthMenu": [[10, 25, 50, -1], ["10", "25", "50", "all"]],
|
||||
"iDisplayLength":25,
|
||||
"aaSorting": [[2, "asc"]],
|
||||
"aoColumnDefs": [ { "bSortable": false, "aTargets": [ 0 ] }],
|
||||
"bAutoWidth": false,
|
||||
columns: [
|
||||
{data: "checkbox"},
|
||||
{data: "id"},
|
||||
{data: "hostname"},
|
||||
{data: "ip"},
|
||||
{data: "type"}
|
||||
]
|
||||
});
|
||||
//将ID列隐藏
|
||||
table.column('1').visible(false);
|
||||
|
||||
$('#editable tbody').on( 'click', 'tr', function () {
|
||||
//alert($(this).hasClass('selected'));
|
||||
if($(this).hasClass('selected')){
|
||||
$(this).removeClass('selected');
|
||||
this.children[0].children[0].checked=0;
|
||||
}else{
|
||||
$(this).addClass('selected');
|
||||
this.children[0].children[0].checked=1;
|
||||
}
|
||||
});
|
||||
|
||||
$('#close-btn').on('click',function(){
|
||||
$('#modal').modal('hide');
|
||||
});
|
||||
var size_name = document.getElementById('asset_on_count').innerText;
|
||||
$('#save-btn').on('click',function(){
|
||||
//alert( table.rows('.selected').data().length +' row(s) selected' );
|
||||
var d = table.rows('.selected').data();
|
||||
var size = d.length;
|
||||
var re = /\d+/;
|
||||
document.getElementById('add_asset').value = size;
|
||||
var str= size_name;
|
||||
var re=/\d+/g;
|
||||
document.getElementById('asset_on_count').innerText = str.replace(re, size);
|
||||
var column2 = table.rows('.selected').data();
|
||||
$("#asset_sed").find("input[name='assets']").remove();
|
||||
$("#asset_sed").find("button[name='asset_hostname']").remove();
|
||||
for(var i=0;i<column2.length;i++){
|
||||
column2[i].checkbox='<input name="checked" value="1" checked="" type="checkbox">';
|
||||
var value = column2[i].id;
|
||||
var ip = column2[i].ip;
|
||||
var hostname = column2[i].hostname;
|
||||
$("#asset_sed").append("<input type='hidden' name='assets' value='"+value+"'>");
|
||||
$("#asset_on_p").append("<button name='asset_hostname' title='"+ip+"' type='button' class='btn btn-default btn-xs ss'>"+hostname+"</button> ");
|
||||
}
|
||||
$('#modal').modal('hide');
|
||||
});
|
||||
|
||||
}); //$(document).ready
|
||||
|
||||
var bCheck = 1;
|
||||
function checkAll(){
|
||||
if(bCheck){
|
||||
$("tr[name='oAssets']").each(function(){
|
||||
oCheckbox = this.children[0].children[0];
|
||||
$(this).toggleClass('selected',true);
|
||||
oCheckbox.checked=1;
|
||||
});
|
||||
document.getElementById('check_all').checked=1;
|
||||
bCheck = 0;
|
||||
}else{
|
||||
$("tr[name='oAssets']").each(function(){
|
||||
oCheckbox = this.children[0].children[0];
|
||||
$(this).toggleClass('selected',false);
|
||||
oCheckbox.checked=0;
|
||||
});
|
||||
document.getElementById('check_all').checked=0;
|
||||
bCheck = 1;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -2,6 +2,8 @@
|
|||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load asset_tags %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% block custom_head_css_js_create %}
|
||||
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
||||
|
@ -20,32 +22,44 @@
|
|||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.cluster layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
{% bootstrap_field form.env layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Group' %}</h3>
|
||||
{% bootstrap_field form.groups layout="horizontal" %}
|
||||
<h3>{% trans 'Node' %}</h3>
|
||||
{% bootstrap_field form.nodes layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Labels' %}</h3>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
|
||||
<div class="col-md-9">
|
||||
<select name="labels" class="select2 labels" data-placeholder="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||
<optgroup label="{{ name }}">
|
||||
{% for label in labels %}
|
||||
{% if label in form.labels.initial %}
|
||||
<option value="{{ label.id }}" selected>{{ label.value }}</option>
|
||||
{% else %}
|
||||
<option value="{{ label.id }}">{{ label.value }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Configuration' %}</h3>
|
||||
{% bootstrap_field form.number layout="horizontal" %}
|
||||
{% bootstrap_field form.remote_card_ip layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Location' %}</h3>
|
||||
{% bootstrap_field form.cabinet_no layout="horizontal" %}
|
||||
{% bootstrap_field form.cabinet_pos layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.status layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||
|
||||
|
@ -62,13 +76,17 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function format(item) {
|
||||
var group = item.element.parentElement.label;
|
||||
return group + ':' + item.text;
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
$("#tags").select2({
|
||||
tags: true,
|
||||
maximumSelectionLength: 8 //最多能够选择的个数
|
||||
$(".labels").select2({
|
||||
allowClear: true,
|
||||
templateSelection: format
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,215 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:cluster-detail' pk=cluster.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="active"><a href="{% url 'assets:cluster-assets' pk=cluster.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Cluster assets' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Cluster assets' %} <b>{{ cluster.name }} </b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover " id="cluster_assets_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Alive' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs" id="btn-test-assets" style="width: 54px">{% trans 'Run' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Add assets to' %} {{ cluster.name }}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset in assets_remain %}
|
||||
<option value="{{ asset.id }}" id="opt_{{ asset.id }}">{{ asset.hostname}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-info btn-sm btn-add-assets">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script>
|
||||
|
||||
jumpserver.assets_selected = {};
|
||||
|
||||
Array.prototype.remove = function(val) {
|
||||
var index = this.indexOf(val);
|
||||
if (index > -1) {
|
||||
this.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
function addAssets(assets) {
|
||||
var the_url = "{% url 'api-assets:cluster-add-assets' pk=cluster.id %}";
|
||||
var body = {
|
||||
assets: assets
|
||||
};
|
||||
var $data_table = $("#cluster_assets_table").DataTable();
|
||||
var success = function(data) {
|
||||
$('.select2-selection__rendered').empty();
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.groups_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PUT',
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#cluster_assets_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', rowData.id);
|
||||
$(td).html(update_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?cluster_id={{ cluster.id }}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on("select2:select", function (evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id];
|
||||
});
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-add-assets', function () {
|
||||
if (Object.keys(jumpserver.assets_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var assets_id = [];
|
||||
$.map(jumpserver.assets_selected, function(value, index) {
|
||||
assets_id.push(index);
|
||||
});
|
||||
|
||||
addAssets(assets_id);
|
||||
})
|
||||
.on('click', '#btn-test-assets', function () {
|
||||
var the_url = "{% url 'api-assets:cluster-test-connective' pk=cluster.id %}";
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success_message: "{% trans 'Task has been send, seen left assets status' %}"
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,76 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-10">
|
||||
<div class="ibox float-e-margins">
|
||||
<div id="ibox-content" class="ibox-title">
|
||||
<h5> {{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ibox-content">
|
||||
<div class="panel blank-panel">
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
<form id="ClusterForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<h3 class="widget-head-color-box">{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.address layout="horizontal" %}
|
||||
{% bootstrap_field form.contact layout="horizontal" %}
|
||||
{% bootstrap_field form.phone layout="horizontal" %}
|
||||
|
||||
<h3 class="widget-head-color-box">{% trans 'Setting' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
{% bootstrap_field form.system_users layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3 class="widget-head-color-box">{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.operator layout="horizontal" %}
|
||||
{% bootstrap_field form.intranet layout="horizontal" %}
|
||||
{% bootstrap_field form.extranet layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,164 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:cluster-detail' pk=cluster.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'assets:cluster-assets' pk=cluster.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Cluster assets' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:cluster-update' pk=cluster.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-delete-cluster">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ cluster.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="20%">{% trans 'Name' %}:</td>
|
||||
<td><b>{{ cluster.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Bandwidth' %}:</td>
|
||||
<td><b>{{ cluster.bandwidth }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Contact' %}:</td>
|
||||
<td><b>{{ cluster.contact }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Phone' %}:</td>
|
||||
<td><b>{{ cluster.phone }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Address' %}:</td>
|
||||
<td><b>{{ cluster.address }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Intranet' %}:</td>
|
||||
<td><b>{{ cluster.Intranet }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Extranet' %}:</td>
|
||||
<td><b>{{ cluster.extranet }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Operator' %}:</td>
|
||||
<td><b>{{ cluster.operator }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ system_user.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset_group.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ system_user.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# <div class="col-sm-4" style="padding-left: 0;padding-right: 0">#}
|
||||
{# <div class="panel panel-primary">#}
|
||||
{# <div class="panel-heading">#}
|
||||
{# <i class="fa fa-info-circle"></i> {% trans 'Update admin user' %}#}
|
||||
{# </div>#}
|
||||
{# <div class="panel-body">#}
|
||||
{# <table class="table cluster_edit" id="add-asset2group">#}
|
||||
{# <tbody>#}
|
||||
{# <form>#}
|
||||
{# <tr>#}
|
||||
{# <td colspan="2" class="no-borders">#}
|
||||
{# <select data-placeholder="{% trans 'Select admin user' %}" id="cluster_selected" class="select2" style="width: 100%" tabindex="4">#}
|
||||
{# {% for admin_user in admin_user_list %}#}
|
||||
{# <option value="{{ admin_user.id }}" id="opt_{{ admin_user.id }}" {% if admin_user.id == cluster.admin_user.id %} selected {% endif %} >{{ admin_user.name }}</option>#}
|
||||
{# {% endfor %}#}
|
||||
{# </select>#}
|
||||
{# </td>#}
|
||||
{# </tr>#}
|
||||
{# <tr>#}
|
||||
{# <td colspan="2" class="no-borders">#}
|
||||
{# <button type="button" class="btn btn-primary btn-sm" id="btn-add-to-cluster">{% trans 'Confirm' %}</button>#}
|
||||
{# </td>#}
|
||||
{# </tr>#}
|
||||
{# </form>#}
|
||||
{##}
|
||||
{# {% for cluster in system_user.cluster.all %}#}
|
||||
{# <tr>#}
|
||||
{# <td ><b class="bdg_cluster" data-gid={{ cluster.id }}>{{ cluster.name }}</b></td>#}
|
||||
{# <td>#}
|
||||
{# <button class="btn btn-danger pull-right btn-xs btn-remove-from-cluster" type="button"><i class="fa fa-minus"></i></button>#}
|
||||
{# </td>#}
|
||||
{# </tr>#}
|
||||
{# {% endfor %}#}
|
||||
{# </tbody>#}
|
||||
{# </table>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
})
|
||||
.on('click', '.btn-delete-cluster', function () {
|
||||
var name = "{{ cluster.name }}";
|
||||
var id = "{{ cluster.id }}";
|
||||
var the_url = '{% url "api-assets:cluster-detail" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", id);
|
||||
var redirect_url = "{% url 'assets:cluster-list' %}";
|
||||
objectDelete(this, name, the_url, redirect_url);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,119 +0,0 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url "assets:cluster-create" %}" class="btn btn-sm btn-primary"> {% trans "Create cluster" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="cluster_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center"><a href="{% url 'assets:cluster-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center">{% trans 'Admin user' %}</th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||
<th class="text-center">{% trans 'System users' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#cluster_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:cluster-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:cluster-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_cluster_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:cluster-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "name" }, {data: "admin_user_name"}, {data: "assets_amount" },
|
||||
{data: "system_users" }, {data: "id" }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', '.btn_cluster_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#cluster_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:cluster-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#cluster_list_table').DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (id_list === []) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-assets:cluster-list' %}";
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected cluster' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'Cluster Deleted.' %}";
|
||||
swal("{% trans 'Cluster delete' %}", msg, "success");
|
||||
$('#cluster_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Cluster deleting failed.' %}";
|
||||
swal("{% trans 'Cluster delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
<form id="groupForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.value layout="horizontal" %}
|
||||
{% bootstrap_field form.assets layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
|
@ -0,0 +1,70 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'assets:label-create' %}" class="btn btn-sm btn-primary"> {% trans "Create label" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="label_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Value' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#label_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{# var detail_btn = '<a href="{% url "assets:label-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';#}
|
||||
var detail_btn = '<a>' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:label-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-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:label-list" %}?sort=name',
|
||||
columns: [
|
||||
{data: "id"}, {data: "name" }, {data: "value" },
|
||||
{data: "asset_count" }, {data: "id"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#label_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:label-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
@ -21,9 +21,6 @@
|
|||
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
<li class="active">
|
||||
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}
|
||||
</a>
|
||||
</li>
|
||||
{# <li>#}
|
||||
{# <a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">#}
|
||||
{# <i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
|
@ -130,6 +130,23 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="50%">{% trans 'Push system user manually' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{# <tr>#}
|
||||
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
||||
{# <td>#}
|
||||
|
@ -144,33 +161,33 @@
|
|||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Clusters' %}
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table cluster_edit" id="add-asset2group">
|
||||
<table class="table node_edit" id="add-asset2group">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add to cluster' %}" id="cluster_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for cluster in cluster_remain %}
|
||||
<option value="{{ cluster.id }}" id="opt_{{ cluster.id }}" >{{ cluster.name }}</option>
|
||||
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes_remain %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-cluster">{% trans 'Confirm' %}</button>
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for cluster in system_user.cluster.all %}
|
||||
{% for node in system_user.nodes.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_cluster" data-gid={{ cluster.id }}>{{ cluster.name }}</b></td>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-remove-from-cluster" 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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -187,27 +204,27 @@
|
|||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function updateSystemUserCluster(clusters) {
|
||||
function updateSystemUserCluster(nodes) {
|
||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||
var body = {
|
||||
cluster: Object.assign([], clusters)
|
||||
nodes: Object.assign([], nodes)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#cluster_selected').val('');
|
||||
$.map(jumpserver.cluster_selected, function(cluster_name, index) {
|
||||
$('#node_selected').val('');
|
||||
$.map(jumpserver.nodes_selected, function(node_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('.cluster_edit tbody').append(
|
||||
$('.node_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_cluster" data-gid="' + index + '">' + cluster_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-cluster" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.cluster_selected = {};
|
||||
jumpserver.nodes_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -215,16 +232,16 @@ function updateSystemUserCluster(clusters) {
|
|||
success: success
|
||||
});
|
||||
}
|
||||
jumpserver.cluster_selected = {};
|
||||
jumpserver.nodes_selected = {};
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.cluster_selected[data.id] = data.text;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.cluster_selected[data.id];
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
})
|
||||
.on('click', '#btn-auto-push', function () {
|
||||
|
@ -238,32 +255,32 @@ $(document).ready(function () {
|
|||
body: JSON.stringify(body)
|
||||
});
|
||||
})
|
||||
.on('click', '#btn-add-to-cluster', function() {
|
||||
if (Object.keys(jumpserver.cluster_selected).length === 0) {
|
||||
.on('click', '#btn-add-to-node', function() {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var clusters = $('.bdg_cluster').map(function() {
|
||||
var nodes = $('.bdg_node').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.cluster_selected, function(value, index) {
|
||||
clusters.push(index);
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
nodes.push(index);
|
||||
});
|
||||
updateSystemUserCluster(clusters);
|
||||
updateSystemUserCluster(nodes);
|
||||
})
|
||||
.on('click', '.btn-remove-from-cluster', function() {
|
||||
.on('click', '.btn-remove-from-node', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_cluster');
|
||||
var $badge = $tr.find('.bdg_node');
|
||||
var gid = $badge.data('gid');
|
||||
var cluster_name = $badge.html() || $badge.text();
|
||||
var node_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + cluster_name + '</option>'
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var clusters = $('.bdg_cluster').map(function () {
|
||||
var nodes = $('.bdg_node').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateSystemUserCluster(clusters);
|
||||
updateSystemUserCluster(nodes);
|
||||
}).on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ system_user.name}}";
|
||||
|
@ -272,5 +289,29 @@ $(document).ready(function () {
|
|||
var redirect_url = "{% url 'assets:system-user-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success_message: "{% trans "Task has been send, Go to ops task list seen result" %}"
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success_message: "{% trans "Task has been send, seen left assets status" %}"
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
系统用户是 用户登录资产(服务器)时使用的用户,如 web, sa, dba等具有特殊功能的用户。系统用户创建时,如果选择了自动推送
|
||||
Jumpserver会使用ansible自动推送到系统用户所在集群的资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。
|
||||
Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -75,7 +75,8 @@ function initTable() {
|
|||
if (val === 100) {
|
||||
innerHtml = "<span class='text-navy'>" + val + "% </span>";
|
||||
} else {
|
||||
innerHtml = "<span class='text-danger'>" + val + "% </span>";
|
||||
var num = new Number(val);
|
||||
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||
<div class="col-sm-8">
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Port' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Env' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Connective' %}</th>
|
||||
|
@ -44,14 +42,14 @@ function initTable() {
|
|||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 8, createdCell: function (td, cellData) {
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (cellData == 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
|
@ -68,8 +66,7 @@ function initTable() {
|
|||
ajax_url: '{% url "api-assets:user-asset-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"},
|
||||
{data: "is_active" }, {data: "is_connective"}
|
||||
{data: "hardware_info"}, {data: "is_active" }, {data: "is_connective"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -1,6 +1,12 @@
|
|||
from collections import defaultdict
|
||||
|
||||
from django import template
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def group_labels(queryset):
|
||||
grouped = defaultdict(list)
|
||||
for label in queryset:
|
||||
grouped[label.name].append(label)
|
||||
return [(name, labels) for name, labels in grouped.items()]
|
||||
|
|
|
@ -7,11 +7,13 @@ app_name = 'assets'
|
|||
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'v1/groups', api.AssetGroupViewSet, 'asset-group')
|
||||
# router.register(r'v1/groups', api.AssetGroupViewSet, 'asset-group')
|
||||
router.register(r'v1/assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'v1/clusters', api.ClusterViewSet, 'cluster')
|
||||
# router.register(r'v1/clusters', api.ClusterViewSet, 'cluster')
|
||||
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'v1/labels', api.LabelViewSet, 'label')
|
||||
router.register(r'v1/nodes', api.NodeViewSet, 'node')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
|
@ -24,23 +26,27 @@ urlpatterns = [
|
|||
url(r'^v1/assets/user-assets/$',
|
||||
api.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
# update the asset group, which add or delete the asset to the group
|
||||
url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.GroupUpdateAssetsApi.as_view(), name='group-update-assets'),
|
||||
url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
|
||||
api.GroupAddAssetsApi.as_view(), name='group-add-assets'),
|
||||
#url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
# api.GroupUpdateAssetsApi.as_view(), name='group-update-assets'),
|
||||
#url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
|
||||
# api.GroupAddAssetsApi.as_view(), name='group-add-assets'),
|
||||
# update the Cluster, and add or delete the assets to the Cluster
|
||||
url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'),
|
||||
url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/connective/$',
|
||||
api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/clusters/$',
|
||||
api.AdminUserAddClustersApi.as_view(), name='admin-user-add-clusters'),
|
||||
#url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
# api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'),
|
||||
#url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/connective/$',
|
||||
# api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -14,27 +14,11 @@ urlpatterns = [
|
|||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'),
|
||||
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
|
||||
# User asset view
|
||||
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
|
||||
# Resource asset group url
|
||||
url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'),
|
||||
url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'),
|
||||
url(r'^asset-group/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'),
|
||||
url(r'^asset-group/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'),
|
||||
url(r'^asset-group/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'),
|
||||
|
||||
# Resource cluster url
|
||||
url(r'^cluster/$', views.ClusterListView.as_view(), name='cluster-list'),
|
||||
url(r'^cluster/create/$', views.ClusterCreateView.as_view(), name='cluster-create'),
|
||||
url(r'^cluster/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.ClusterDetailView.as_view(), name='cluster-detail'),
|
||||
url(r'^cluster/(?P<pk>[0-9a-zA-Z\-]{36})/update/', views.ClusterUpdateView.as_view(), name='cluster-update'),
|
||||
url(r'^cluster/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.ClusterDeleteView.as_view(), name='cluster-delete'),
|
||||
url(r'^cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.ClusterAssetsView.as_view(), name='cluster-assets'),
|
||||
|
||||
# Resource admin user url
|
||||
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
|
@ -50,8 +34,10 @@ urlpatterns = [
|
|||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
# url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset-group$', views.SystemUserAssetGroupView.as_view(),
|
||||
# name='system-user-asset-group'),
|
||||
|
||||
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
|
||||
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
from collections import defaultdict
|
||||
from functools import reduce
|
||||
import operator
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from .models import Asset, SystemUser
|
||||
from .models import Asset, SystemUser, Label
|
||||
|
||||
|
||||
def get_assets_by_id_list(id_list):
|
||||
|
@ -18,12 +23,26 @@ def get_system_user_by_name(name):
|
|||
return system_user
|
||||
|
||||
|
||||
def check_assets_have_system_user(assets, system_users):
|
||||
errors = defaultdict(list)
|
||||
class LabelFilter:
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
query_keys = self.request.query_params.keys()
|
||||
all_label_keys = Label.objects.values_list('name', flat=True)
|
||||
valid_keys = set(all_label_keys) & set(query_keys)
|
||||
labels_query = {}
|
||||
for key in valid_keys:
|
||||
labels_query[key] = self.request.query_params.get(key)
|
||||
|
||||
conditions = []
|
||||
for k, v in labels_query.items():
|
||||
query = {'labels__name': k, 'labels__value': v}
|
||||
conditions.append(query)
|
||||
|
||||
if conditions:
|
||||
for kwargs in conditions:
|
||||
queryset = queryset.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
|
||||
|
||||
|
||||
for system_user in system_users:
|
||||
clusters = system_user.cluster.all()
|
||||
for asset in assets:
|
||||
if asset.cluster not in clusters:
|
||||
errors[asset].append(system_user)
|
||||
return errors
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# coding:utf-8
|
||||
from .asset import *
|
||||
from .group import *
|
||||
from .cluster import *
|
||||
from .system_user import *
|
||||
from .admin_user import *
|
||||
|
||||
from .label import *
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
|
|||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import AdminUser, Cluster
|
||||
from ..models import AdminUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
__all__ = [
|
||||
|
@ -74,11 +74,10 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
|
|||
object = None
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
cluster_remain = Cluster.objects.exclude(admin_user=self.object)
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Admin user detail'),
|
||||
'cluster_remain': cluster_remain,
|
||||
'nodes': Node.objects.all()
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -95,11 +94,8 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
|||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
for cluster in self.object.cluster_set.all():
|
||||
queryset.extend([asset for asset in cluster.assets.all() if not asset.admin_user])
|
||||
self.queryset = queryset
|
||||
return queryset
|
||||
self.queryset = self.object.asset_set.all()
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
|
|
@ -27,15 +27,14 @@ from common.mixins import JSONResponseMixin
|
|||
from common.utils import get_object_or_none, get_logger, is_uuid
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser, Label, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetListView', 'AssetCreateView', 'AssetUpdateView',
|
||||
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
||||
'AssetModalListView', 'AssetDeleteView', 'AssetExportView',
|
||||
'BulkImportAssetView',
|
||||
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView',
|
||||
]
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -44,10 +43,11 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
|
|||
template_name = 'assets/asset_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
Node.root()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset list'),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
'labels': Label.objects.all().order_by('name'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -58,8 +58,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset list'),
|
||||
'action': _('My assets'),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
@ -72,12 +71,23 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
template_name = 'assets/asset_create.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
asset = form.save()
|
||||
asset.created_by = self.request.user.username or 'Admin'
|
||||
asset.date_created = timezone.now()
|
||||
asset.save()
|
||||
return super().form_valid(form)
|
||||
# def form_valid(self, form):
|
||||
# print("form valid")
|
||||
# asset = form.save()
|
||||
# asset.created_by = self.request.user.username or 'Admin'
|
||||
# asset.date_created = timezone.now()
|
||||
# asset.save()
|
||||
# return super().form_valid(form)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
node_id = self.request.GET.get("node_id")
|
||||
if node_id:
|
||||
node = get_object_or_none(Node, id=node_id)
|
||||
else:
|
||||
node = Node.root()
|
||||
form["nodes"].initial = node
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -91,22 +101,22 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
model = Asset
|
||||
context_object_name = 'asset_modal_list'
|
||||
template_name = 'assets/asset_modal_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets = Asset.objects.all()
|
||||
assets_id = self.request.GET.get('assets_id', '')
|
||||
assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
|
||||
context = {
|
||||
'all_assets': assets_id_list,
|
||||
'assets': assets
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
# class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
# paginate_by = settings.DISPLAY_PER_PAGE
|
||||
# model = Asset
|
||||
# context_object_name = 'asset_modal_list'
|
||||
# template_name = 'assets/_asset_list_modal.html'
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# assets = Asset.objects.all()
|
||||
# assets_id = self.request.GET.get('assets_id', '')
|
||||
# assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
|
||||
# context = {
|
||||
# 'all_assets': assets_id_list,
|
||||
# 'assets': assets
|
||||
# }
|
||||
# kwargs.update(context)
|
||||
# return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||
|
@ -180,14 +190,11 @@ class AssetDetailView(DetailView):
|
|||
template_name = 'assets/asset_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
asset_groups = self.object.groups.all()
|
||||
nodes_remain = Node.objects.exclude(assets=self.object)
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset detail'),
|
||||
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
|
||||
if asset_group not in asset_groups],
|
||||
'asset_groups': asset_groups,
|
||||
'system_users_all': SystemUser.objects.all(),
|
||||
'nodes_remain': nodes_remain,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -206,22 +213,19 @@ class AssetExportView(View):
|
|||
]
|
||||
]
|
||||
filename = 'assets-{}.csv'.format(
|
||||
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')
|
||||
)
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
response.write(codecs.BOM_UTF8)
|
||||
assets = Asset.objects.filter(id__in=assets_id)
|
||||
writer = csv.writer(response, dialect='excel',
|
||||
quoting=csv.QUOTE_MINIMAL)
|
||||
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
header = [field.verbose_name for field in fields]
|
||||
header.append(_('Asset groups'))
|
||||
writer.writerow(header)
|
||||
|
||||
for asset in assets:
|
||||
groups = ','.join([group.name for group in asset.groups.all()])
|
||||
data = [getattr(asset, field.name) for field in fields]
|
||||
data.append(groups)
|
||||
writer.writerow(data)
|
||||
return response
|
||||
|
||||
|
@ -243,6 +247,7 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
f = form.cleaned_data['file']
|
||||
det_result = chardet.detect(f.read())
|
||||
f.seek(0) # reset file seek index
|
||||
|
||||
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
||||
csv_file = StringIO(file_data)
|
||||
reader = csv.reader(csv_file)
|
||||
|
@ -255,7 +260,6 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
]
|
||||
header_ = csv_data[0]
|
||||
mapping_reverse = {field.verbose_name: field.name for field in fields}
|
||||
mapping_reverse[_('Asset groups')] = 'groups'
|
||||
attr = [mapping_reverse.get(n, None) for n in header_]
|
||||
if None in attr:
|
||||
data = {'valid': False,
|
||||
|
@ -272,20 +276,15 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
asset_dict = dict(zip(attr, row))
|
||||
id_ = asset_dict.pop('id', 0)
|
||||
for k, v in asset_dict.items():
|
||||
if k == 'cluster':
|
||||
v = get_object_or_none(Cluster, name=v)
|
||||
elif k == 'is_active':
|
||||
v = bool(v)
|
||||
if k == 'is_active':
|
||||
v = True if v in ['TRUE', 1, 'true'] else False
|
||||
elif k == 'admin_user':
|
||||
v = get_object_or_none(AdminUser, name=v)
|
||||
elif k in ['port', 'cabinet_pos', 'cpu_count', 'cpu_cores']:
|
||||
elif k in ['port', 'cpu_count', 'cpu_cores']:
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
v = 0
|
||||
elif k == 'groups':
|
||||
groups_name = v.split(',')
|
||||
v = AssetGroup.objects.filter(name__in=groups_name)
|
||||
else:
|
||||
continue
|
||||
asset_dict[k] = v
|
||||
|
@ -293,20 +292,15 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
asset = get_object_or_none(Asset, id=id_) if is_uuid(id_) else None
|
||||
if not asset:
|
||||
try:
|
||||
groups = asset_dict.pop('groups')
|
||||
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
|
||||
raise Exception(_('already exists'))
|
||||
asset = Asset.objects.create(**asset_dict)
|
||||
asset.groups.set(groups)
|
||||
created.append(asset_dict['hostname'])
|
||||
assets.append(asset)
|
||||
except Exception as e:
|
||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
||||
else:
|
||||
for k, v in asset_dict.items():
|
||||
if k == 'groups':
|
||||
asset.groups.set(v)
|
||||
continue
|
||||
if v:
|
||||
setattr(asset, k, v)
|
||||
try:
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
# coding:utf-8
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ClusterListView', 'ClusterCreateView', 'ClusterUpdateView',
|
||||
'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView',
|
||||
]
|
||||
|
||||
|
||||
class ClusterListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/cluster_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Cluster list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ClusterCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = Cluster
|
||||
form_class = forms.ClusterForm
|
||||
template_name = 'assets/cluster_create_update.html'
|
||||
success_url = reverse_lazy('assets:cluster-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('Create cluster'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
cluster = form.save()
|
||||
cluster.created_by = self.request.user.username
|
||||
cluster.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = Cluster
|
||||
form_class = forms.ClusterForm
|
||||
template_name = 'assets/cluster_create_update.html'
|
||||
context_object_name = 'cluster'
|
||||
success_url = reverse_lazy('assets:cluster-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def form_valid(self, form):
|
||||
cluster = form.save(commit=False)
|
||||
cluster.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('Update Cluster'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ClusterDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = Cluster
|
||||
template_name = 'assets/cluster_detail.html'
|
||||
context_object_name = 'cluster'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
admin_user_list = AdminUser.objects.exclude()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Cluster detail'),
|
||||
'admin_user_list': admin_user_list,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ClusterAssetsView(AdminUserRequiredMixin, DetailView):
|
||||
model = Cluster
|
||||
template_name = 'assets/cluster_assets.html'
|
||||
context_object_name = 'cluster'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets_remain = Asset.objects.exclude(id__in=self.object.assets.all())
|
||||
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset detail'),
|
||||
'groups': AssetGroup.objects.all(),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
'assets_remain': assets_remain,
|
||||
'assets': [asset for asset in Asset.objects.all() if asset not in assets_remain],
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ClusterDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Cluster
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:cluster-list')
|
|
@ -1,97 +0,0 @@
|
|||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.shortcuts import get_object_or_404, reverse, redirect
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetGroupCreateView', 'AssetGroupDetailView',
|
||||
'AssetGroupUpdateView', 'AssetGroupListView',
|
||||
'AssetGroupDeleteView',
|
||||
]
|
||||
|
||||
|
||||
class AssetGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = AssetGroup
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset group'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
group = form.save()
|
||||
group.created_by = self.request.user.username
|
||||
group.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class AssetGroupListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/asset_group_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset group list'),
|
||||
'assets': Asset.objects.all(),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = AssetGroup
|
||||
template_name = 'assets/asset_group_detail.html'
|
||||
context_object_name = 'asset_group'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets_remain = Asset.objects.exclude(groups__in=[self.object])
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset group detail'),
|
||||
'assets_remain': assets_remain,
|
||||
'assets': self.object.assets.all(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = AssetGroup
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset group'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
template_name = 'delete_confirm.html'
|
||||
model = AssetGroup
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.views.generic import TemplateView, CreateView, \
|
||||
UpdateView, DeleteView, DetailView
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import Label
|
||||
from ..forms import LabelForm
|
||||
|
||||
|
||||
__all__ = (
|
||||
"LabelListView", "LabelCreateView", "LabelUpdateView",
|
||||
"LabelDetailView", "LabelDeleteView",
|
||||
)
|
||||
|
||||
|
||||
class LabelListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/label_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Label list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = Label
|
||||
template_name = 'assets/label_create_update.html'
|
||||
form_class = LabelForm
|
||||
success_url = reverse_lazy('assets:label-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create label'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = Label
|
||||
template_name = 'assets/label_create_update.html'
|
||||
form_class = LabelForm
|
||||
success_url = reverse_lazy('assets:label-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update label'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LabelDetailView(AdminUserRequiredMixin, DetailView):
|
||||
pass
|
||||
|
||||
|
||||
class LabelDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Label
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:label-list')
|
|
@ -8,8 +8,8 @@ from django.contrib.messages.views import SuccessMessageMixin
|
|||
from django.views.generic.detail import DetailView
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..forms import SystemUserForm, SystemUserUpdateForm
|
||||
from ..models import SystemUser, Cluster
|
||||
from ..forms import SystemUserForm
|
||||
from ..models import SystemUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
|
@ -50,7 +50,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
|
|||
|
||||
class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = SystemUser
|
||||
form_class = SystemUserUpdateForm
|
||||
form_class = SystemUserForm
|
||||
template_name = 'assets/system_user_update.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
success_message = update_success_msg
|
||||
|
@ -70,11 +70,10 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
|||
model = SystemUser
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
cluster_remain = Cluster.objects.exclude(systemuser=self.object)
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('System user detail'),
|
||||
'cluster_remain': cluster_remain,
|
||||
'nodes_remain': Node.objects.exclude(systemuser=self.object)
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.core.mail import get_connection, send_mail
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from .permissions import IsSuperUser
|
||||
from .permissions import IsSuperUser, IsAppUser
|
||||
from .serializers import MailTestSerializer, LDAPTestSerializer
|
||||
|
||||
|
||||
|
@ -63,8 +63,6 @@ class LDAPTestingAPI(APIView):
|
|||
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
|
||||
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
|
||||
|
||||
print(serializer.validated_data)
|
||||
|
||||
try:
|
||||
attr_map = json.loads(attr_map)
|
||||
except json.JSONDecodeError:
|
||||
|
@ -77,9 +75,6 @@ class LDAPTestingAPI(APIView):
|
|||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=401)
|
||||
|
||||
print(search_ou)
|
||||
print(search_filter % ({"user": "*"}))
|
||||
print(attr_map.values())
|
||||
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
|
||||
attributes=list(attr_map.values()))
|
||||
if not ok:
|
||||
|
@ -93,7 +88,7 @@ class LDAPTestingAPI(APIView):
|
|||
user[attr] = getattr(entry, mapping)
|
||||
users.append(user)
|
||||
if len(users) > 0:
|
||||
return Response({"msg": "Match {} s users".format(len(users))})
|
||||
return Response({"msg": _("Match {} s users").format(len(users))})
|
||||
else:
|
||||
return Response({"error": "Have user but attr mapping error"}, status=401)
|
||||
else:
|
||||
|
@ -102,9 +97,11 @@ class LDAPTestingAPI(APIView):
|
|||
|
||||
class DjangoSettingsAPI(APIView):
|
||||
def get(self, request):
|
||||
if not settings.DEBUG:
|
||||
return Response('Only debug mode support')
|
||||
|
||||
configs = {}
|
||||
for i in dir(settings):
|
||||
if i.isupper():
|
||||
configs[i] = str(getattr(settings, i))
|
||||
return Response(configs)
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import json
|
|||
from django import forms
|
||||
from django.utils import six
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class DictField(forms.Field):
|
||||
|
@ -18,16 +20,16 @@ class DictField(forms.Field):
|
|||
# we don't need to handle that explicitly.
|
||||
if isinstance(value, six.string_types):
|
||||
try:
|
||||
print(value)
|
||||
value = json.loads(value)
|
||||
return value
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
value = {}
|
||||
return value
|
||||
return ValidationError(_("Not a valid json"))
|
||||
else:
|
||||
return ValidationError(_("Not a string type"))
|
||||
|
||||
def validate(self, value):
|
||||
print(value)
|
||||
if isinstance(value, ValidationError):
|
||||
raise value
|
||||
if not value and self.required:
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
|
||||
|
@ -35,3 +37,9 @@ class DictField(forms.Field):
|
|||
# Sometimes data or initial may be a string equivalent of a boolean
|
||||
# so we should run it through to_python first to get a boolean value
|
||||
return self.to_python(initial) != self.to_python(data)
|
||||
|
||||
|
||||
class StringIDField(serializers.Field):
|
||||
def to_representation(self, value):
|
||||
return {"pk": value.pk, "name": value.__str__()}
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import json
|
|||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.html import escape
|
||||
from django.db import transaction
|
||||
from django.conf import settings
|
||||
|
||||
from .models import Setting
|
||||
from .fields import DictField
|
||||
|
@ -24,34 +26,38 @@ def to_form_value(value):
|
|||
data = value
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
return ''
|
||||
return ""
|
||||
|
||||
|
||||
class BaseForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
settings = Setting.objects.all()
|
||||
db_settings = Setting.objects.all()
|
||||
for name, field in self.fields.items():
|
||||
db_value = getattr(settings, name).value
|
||||
if db_value:
|
||||
db_value = getattr(db_settings, name).value
|
||||
django_value = getattr(settings, name) if hasattr(settings, name) else None
|
||||
if db_value is False or db_value:
|
||||
field.initial = to_form_value(db_value)
|
||||
elif django_value is False or django_value:
|
||||
field.initial = to_form_value(to_model_value(django_value))
|
||||
|
||||
def save(self):
|
||||
def save(self, category="default"):
|
||||
if not self.is_bound:
|
||||
raise ValueError("Form is not bound")
|
||||
|
||||
settings = Setting.objects.all()
|
||||
db_settings = Setting.objects.all()
|
||||
if self.is_valid():
|
||||
with transaction.atomic():
|
||||
for name, value in self.cleaned_data.items():
|
||||
field = self.fields[name]
|
||||
if isinstance(field.widget, forms.PasswordInput) and not value:
|
||||
continue
|
||||
if value == to_form_value(getattr(settings, name).value):
|
||||
if value == to_form_value(getattr(db_settings, name).value):
|
||||
continue
|
||||
|
||||
defaults = {
|
||||
'name': name,
|
||||
'category': category,
|
||||
'value': to_model_value(value)
|
||||
}
|
||||
Setting.objects.update_or_create(defaults=defaults, name=name)
|
||||
|
@ -62,10 +68,10 @@ class BaseForm(forms.Form):
|
|||
class BasicSettingForm(BaseForm):
|
||||
SITE_URL = forms.URLField(
|
||||
label=_("Current SITE URL"),
|
||||
help_text="http://jumpserver.abc.com:8080"
|
||||
help_text="eg: http://jumpserver.abc.com:8080"
|
||||
)
|
||||
USER_GUIDE_URL = forms.URLField(
|
||||
label=_("User Guide URL"),
|
||||
label=_("User Guide URL"), required=False,
|
||||
help_text=_("User first login update profile done redirect to it")
|
||||
)
|
||||
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
||||
|
@ -111,7 +117,8 @@ class LDAPSettingForm(BaseForm):
|
|||
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org'
|
||||
)
|
||||
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
|
||||
label=_("User search filter"), initial='(cn=%(user)s)'
|
||||
label=_("User search filter"), initial='(cn=%(user)s)',
|
||||
help_text=_("User search filter must contain ([cn,uid,sAMAccountName,...]=%(user)s)")
|
||||
)
|
||||
AUTH_LDAP_USER_ATTR_MAP = DictField(
|
||||
label=_("User attr map"),
|
||||
|
@ -119,13 +126,45 @@ class LDAPSettingForm(BaseForm):
|
|||
"username": "cn",
|
||||
"name": "sn",
|
||||
"email": "mail"
|
||||
})
|
||||
}),
|
||||
help_text=_(
|
||||
"User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr")
|
||||
)
|
||||
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||
AUTH_LDAP_START_TLS = forms.BooleanField(
|
||||
label=_("Use SSL"), initial=False, required=False
|
||||
)
|
||||
AUTH_LDAP = forms.BooleanField(
|
||||
label=_("Enable LDAP Auth"), initial=False, required=False
|
||||
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), initial=False, required=False)
|
||||
|
||||
|
||||
class TerminalSettingForm(BaseForm):
|
||||
SORT_BY_CHOICES = (
|
||||
('hostname', _('Hostname')),
|
||||
('ip', _('IP')),
|
||||
)
|
||||
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
|
||||
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
|
||||
)
|
||||
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
|
||||
initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
|
||||
)
|
||||
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
|
||||
initial=True, required=False, label=_("Password auth")
|
||||
)
|
||||
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
|
||||
initial=True, required=False, label=_("Public key auth")
|
||||
)
|
||||
TERMINAL_COMMAND_STORAGE = DictField(
|
||||
label=_("Command storage"), help_text=_(
|
||||
"Set terminal storage setting, `default` is the using as default,"
|
||||
"You can set other storage and some terminal using"
|
||||
)
|
||||
)
|
||||
TERMINAL_REPLAY_STORAGE = DictField(
|
||||
label=_("Replay storage"), help_text=_(
|
||||
"Set replay storage setting, `default` is the using as default,"
|
||||
"You can set other storage and some terminal using"
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -47,9 +47,9 @@ class JSONResponseMixin(object):
|
|||
return JsonResponse(context)
|
||||
|
||||
|
||||
class CustomFilterMixin(object):
|
||||
class IDInFilterMixin(object):
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super(CustomFilterMixin, self).filter_queryset(queryset)
|
||||
queryset = super(IDInFilterMixin, self).filter_queryset(queryset)
|
||||
id_list = self.request.query_params.get('id__in')
|
||||
if id_list:
|
||||
import json
|
||||
|
@ -99,9 +99,8 @@ class DatetimeSearchMixin:
|
|||
|
||||
if date_from_s:
|
||||
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
||||
self.date_from = date_from.replace(
|
||||
tzinfo=timezone.get_current_timezone()
|
||||
)
|
||||
tz = timezone.get_current_timezone()
|
||||
self.date_from = tz.localize(date_from)
|
||||
else:
|
||||
self.date_from = timezone.now() - timezone.timedelta(7)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import json
|
|||
|
||||
import ldap
|
||||
from django.db import models
|
||||
from django.db.utils import ProgrammingError, OperationalError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
@ -24,6 +25,7 @@ class SettingManager(models.Manager):
|
|||
class Setting(models.Model):
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
||||
value = models.TextField(verbose_name=_("Value"))
|
||||
category = models.CharField(max_length=128, default="default")
|
||||
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
|
||||
comment = models.TextField(verbose_name=_("Comment"))
|
||||
|
||||
|
@ -33,17 +35,28 @@ class Setting(models.Model):
|
|||
return self.name
|
||||
|
||||
@property
|
||||
def value_(self):
|
||||
def cleaned_value(self):
|
||||
try:
|
||||
return json.loads(self.value)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
@cleaned_value.setter
|
||||
def cleaned_value(self, item):
|
||||
try:
|
||||
v = json.dumps(item)
|
||||
self.value = v
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError("Json dump error: {}".format(str(e)))
|
||||
|
||||
@classmethod
|
||||
def refresh_all_settings(cls):
|
||||
try:
|
||||
settings_list = cls.objects.all()
|
||||
for setting in settings_list:
|
||||
setting.refresh_setting()
|
||||
except (ProgrammingError, OperationalError):
|
||||
pass
|
||||
|
||||
def refresh_setting(self):
|
||||
try:
|
||||
|
@ -53,9 +66,9 @@ class Setting(models.Model):
|
|||
setattr(settings, self.name, value)
|
||||
|
||||
if self.name == "AUTH_LDAP":
|
||||
if self.value_ and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
|
||||
if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
|
||||
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
|
||||
elif not self.value_ and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
|
||||
elif not self.cleaned_value and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
|
||||
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
|
||||
|
||||
if self.name == "AUTH_LDAP_SEARCH_FILTER":
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<li>
|
||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<li>
|
||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<li class="active">
|
||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i
|
||||
class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'settings:email-setting' %}" class="text-center"><i
|
||||
class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i
|
||||
class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
|
||||
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<h3>{% trans "Basic setting" %}</h3>
|
||||
{% for field in form %}
|
||||
{% if not field.field|is_bool_field %}
|
||||
{% bootstrap_field field layout="horizontal" %}
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}"
|
||||
class="col-sm-2 control-label">{{ field.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="col-sm-1">
|
||||
{{ field }}
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<span class="help-block">{{ field.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans "Command storage" %}</h3>
|
||||
<table class="table table-hover " id="task-history-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for name, setting in command_storage.items %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ setting.TYPE }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans "Replay storage" %}</h3>
|
||||
<table class="table table-hover " id="task-history-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for name, setting in replay_storage.items %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ setting.TYPE }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary"
|
||||
type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on("click", ".btn-test", function () {
|
||||
var data = {};
|
||||
var form = $("form").serializeArray();
|
||||
$.each(form, function (i, field) {
|
||||
data[field.name] = field.value;
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-common:ldap-testing' %}";
|
||||
|
||||
function error(message) {
|
||||
toastr.error(message)
|
||||
}
|
||||
|
||||
function success(message) {
|
||||
toastr.success(message.msg)
|
||||
}
|
||||
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(data),
|
||||
method: "POST",
|
||||
flash_message: false,
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
})
|
||||
.on('click', '', function () {
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -73,7 +73,12 @@ def to_html(s):
|
|||
|
||||
@register.filter
|
||||
def time_util_with_seconds(date_from, date_to):
|
||||
if date_from and date_to:
|
||||
if not date_from:
|
||||
return ''
|
||||
if not date_to:
|
||||
return ''
|
||||
date_to = timezone.now()
|
||||
|
||||
delta = date_to - date_from
|
||||
seconds = delta.seconds
|
||||
if seconds < 60:
|
||||
|
@ -82,8 +87,6 @@ def time_util_with_seconds(date_from, date_to):
|
|||
return '{} m'.format(seconds//60)
|
||||
else:
|
||||
return '{} h'.format(seconds//3600)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
|
@ -92,3 +95,8 @@ def is_bool_field(field):
|
|||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@register.filter
|
||||
def to_dict(data):
|
||||
return dict(data)
|
||||
|
|
|
@ -10,4 +10,5 @@ urlpatterns = [
|
|||
url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'),
|
||||
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
||||
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
|
||||
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
|
||||
]
|
||||
|
|
|
@ -238,9 +238,10 @@ def content_md5(data):
|
|||
"""
|
||||
if isinstance(data, str):
|
||||
data = hashlib.md5(data.encode('utf-8'))
|
||||
value = base64.b64encode(data.digest())
|
||||
value = base64.b64encode(data.hexdigest().encode('utf-8'))
|
||||
return value.decode('utf-8')
|
||||
|
||||
|
||||
_STRPTIME_LOCK = threading.Lock()
|
||||
|
||||
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
from django.views.generic import View, TemplateView
|
||||
from django.views.generic import TemplateView
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
|
||||
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm
|
||||
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
|
||||
TerminalSettingForm
|
||||
from .models import Setting
|
||||
from .mixins import AdminUserRequiredMixin
|
||||
from .signals import ldap_auth_enable
|
||||
|
||||
|
@ -86,3 +89,34 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
|
|||
context = self.get_context_data()
|
||||
context.update({"form": form})
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
|
||||
class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
|
||||
form_class = TerminalSettingForm
|
||||
template_name = "common/terminal_setting.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
command_storage = settings.TERMINAL_COMMAND_STORAGE
|
||||
replay_storage = settings.TERMINAL_REPLAY_STORAGE
|
||||
context = {
|
||||
'app': _('Settings'),
|
||||
'action': _('Terminal setting'),
|
||||
'form': self.form_class(),
|
||||
'replay_storage': replay_storage,
|
||||
'command_storage': command_storage,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def post(self, request):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
msg = _("Update setting successfully, please restart program")
|
||||
messages.success(request, msg)
|
||||
return redirect('settings:terminal-setting')
|
||||
else:
|
||||
context = self.get_context_data()
|
||||
context.update({"form": form})
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -17,7 +17,6 @@ import ldap
|
|||
from django_auth_ldap.config import LDAPSearch
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||
|
@ -39,11 +38,9 @@ SECRET_KEY = CONFIG.SECRET_KEY
|
|||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = CONFIG.DEBUG or False
|
||||
|
||||
|
||||
# Absolute url for some case, for example email link
|
||||
SITE_URL = CONFIG.SITE_URL or 'http://localhost'
|
||||
|
||||
|
||||
# LOG LEVEL
|
||||
LOG_LEVEL = 'DEBUG' if DEBUG else CONFIG.LOG_LEVEL or 'WARNING'
|
||||
|
||||
|
@ -114,7 +111,7 @@ LOGIN_URL = reverse_lazy('users:login')
|
|||
|
||||
SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN or None
|
||||
CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN or None
|
||||
SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE or 3600*24
|
||||
SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE or 3600 * 24
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
# Database
|
||||
|
@ -241,7 +238,7 @@ USE_L10N = True
|
|||
USE_TZ = True
|
||||
|
||||
# I18N translation
|
||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'), ]
|
||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'i18n'), ]
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||
|
@ -250,7 +247,6 @@ STATIC_URL = '/static/'
|
|||
STATIC_ROOT = os.path.join(PROJECT_DIR, "data", "static")
|
||||
STATIC_DIR = os.path.join(BASE_DIR, "static")
|
||||
|
||||
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
)
|
||||
|
@ -265,7 +261,7 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/'
|
|||
# BOOTSTRAP_COLUMN_COUNT = 11
|
||||
|
||||
# Init data or generate fake data source for development
|
||||
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'),]
|
||||
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ]
|
||||
|
||||
# Email config
|
||||
EMAIL_HOST = CONFIG.EMAIL_HOST
|
||||
|
@ -298,7 +294,7 @@ REST_FRAMEWORK = {
|
|||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
||||
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'],
|
||||
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
'PAGE_SIZE': 15
|
||||
# 'PAGE_SIZE': 15
|
||||
}
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
|
@ -308,7 +304,6 @@ AUTHENTICATION_BACKENDS = [
|
|||
# Custom User Auth model
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
|
||||
# Auth LDAP settings
|
||||
AUTH_LDAP = CONFIG.AUTH_LDAP
|
||||
AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI
|
||||
|
@ -332,7 +327,6 @@ AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
|||
if AUTH_LDAP:
|
||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
||||
|
||||
|
||||
# Celery using redis as broker
|
||||
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
|
||||
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
||||
|
@ -354,7 +348,6 @@ CELERY_REDIRECT_STDOUTS = True
|
|||
CELERY_REDIRECT_STDOUTS_LEVEL = "INFO"
|
||||
CELERY_WORKER_HIJACK_ROOT_LOGGER = False
|
||||
|
||||
|
||||
# Cache use redis
|
||||
CACHES = {
|
||||
'default': {
|
||||
|
@ -373,7 +366,25 @@ CAPTCHA_FOREGROUND_COLOR = '#001100'
|
|||
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
|
||||
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE
|
||||
|
||||
COMMAND_STORAGE_BACKEND = 'terminal.backends.command.db'
|
||||
COMMAND_STORAGE = {
|
||||
'ENGINE': 'terminal.backends.command.db',
|
||||
}
|
||||
|
||||
TERMINAL_COMMAND_STORAGE = {
|
||||
"default": {
|
||||
"TYPE": "server",
|
||||
},
|
||||
# 'ali-es': {
|
||||
# 'TYPE': 'elasticsearch',
|
||||
# 'HOSTS': ['http://elastic:changeme@localhost:9200'],
|
||||
# },
|
||||
}
|
||||
|
||||
TERMINAL_REPLAY_STORAGE = {
|
||||
"default": {
|
||||
"TYPE": "server",
|
||||
},
|
||||
}
|
||||
|
||||
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
||||
BOOTSTRAP3 = {
|
||||
|
@ -386,6 +397,6 @@ BOOTSTRAP3 = {
|
|||
}
|
||||
|
||||
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
|
||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
|
||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
|
||||
DEFAULT_EXPIRED_YEARS = 70
|
||||
USER_GUIDE_URL = ""
|
||||
|
|
|
@ -4,16 +4,16 @@ from __future__ import unicode_literals
|
|||
from django.conf.urls import url, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.views.static import serve as static_serve
|
||||
|
||||
from rest_framework.schemas import get_schema_view
|
||||
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
|
||||
|
||||
from .views import IndexView
|
||||
from .views import IndexView, LunaView
|
||||
|
||||
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
|
||||
urlpatterns = [
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^luna/', LunaView.as_view(), name='luna-error'),
|
||||
url(r'^users/', include('users.urls.views_urls', namespace='users')),
|
||||
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
||||
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
from django.views.generic import TemplateView
|
||||
import datetime
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.views.generic import TemplateView, View
|
||||
from django.utils import timezone
|
||||
from django.db.models import Count
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
@ -45,15 +48,22 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
return self.session_week.values('user').distinct().count()
|
||||
|
||||
def get_week_login_asset_count(self):
|
||||
return self.session_week.values('asset').distinct().count()
|
||||
return self.session_week.count()
|
||||
# return self.session_week.values('asset').distinct().count()
|
||||
|
||||
def get_month_day_metrics(self):
|
||||
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
||||
return month_str
|
||||
|
||||
def get_month_login_metrics(self):
|
||||
return [self.session_month.filter(date_start__date=d).count()
|
||||
for d in self.session_month_dates]
|
||||
data = []
|
||||
time_min = datetime.datetime.min.time()
|
||||
time_max = datetime.datetime.max.time()
|
||||
for d in self.session_month_dates:
|
||||
ds = datetime.datetime.combine(d, time_min).replace(tzinfo=timezone.get_current_timezone())
|
||||
de = datetime.datetime.combine(d, time_max).replace(tzinfo=timezone.get_current_timezone())
|
||||
data.append(self.session_month.filter(date_start__range=(ds, de)).count())
|
||||
return data
|
||||
|
||||
def get_month_active_user_metrics(self):
|
||||
if self.session_month_dates_archive:
|
||||
|
@ -119,10 +129,18 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
self.session_week = Session.objects.filter(date_start__gt=week_ago)
|
||||
self.session_month = Session.objects.filter(date_start__gt=month_ago)
|
||||
self.session_month_dates = self.session_month.dates('date_start', 'day')
|
||||
self.session_month_dates_archive = [
|
||||
self.session_month.filter(date_start__date=d)
|
||||
for d in self.session_month_dates
|
||||
]
|
||||
|
||||
self.session_month_dates_archive = []
|
||||
time_min = datetime.datetime.min.time()
|
||||
time_max = datetime.datetime.max.time()
|
||||
|
||||
for d in self.session_month_dates:
|
||||
ds = datetime.datetime.combine(d, time_min).replace(
|
||||
tzinfo=timezone.get_current_timezone())
|
||||
de = datetime.datetime.combine(d, time_max).replace(
|
||||
tzinfo=timezone.get_current_timezone())
|
||||
self.session_month_dates_archive.append(
|
||||
self.session_month.filter(date_start__range=(ds, de)))
|
||||
|
||||
context = {
|
||||
'assets_count': self.get_asset_count(),
|
||||
|
@ -149,3 +167,12 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
|
||||
kwargs.update(context)
|
||||
return super(IndexView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LunaView(View):
|
||||
def get(self, request):
|
||||
msg = """
|
||||
Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发,
|
||||
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运
|
||||
"""
|
||||
return HttpResponse(msg)
|
Binary file not shown.
|
@ -43,7 +43,7 @@ def update_or_create_ansible_task(
|
|||
new_adhoc.become = become_info
|
||||
|
||||
if not adhoc or adhoc != new_adhoc:
|
||||
logger.debug("Task create new adhoc: {}".format(task_name))
|
||||
print("Task create new adhoc: {}".format(task_name))
|
||||
new_adhoc.save()
|
||||
task.latest_adhoc = new_adhoc
|
||||
created = True
|
||||
|
|
|
@ -3,17 +3,14 @@
|
|||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
|
||||
from rest_framework.generics import ListAPIView, get_object_or_404
|
||||
from rest_framework import viewsets
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from users.permissions import IsValidUser, IsSuperUser, IsAppUser, IsSuperUserOrAppUser
|
||||
from .utils import get_user_granted_assets, get_user_granted_asset_groups, \
|
||||
get_user_asset_permissions, get_user_group_asset_permissions, \
|
||||
get_user_group_granted_assets, get_user_group_granted_asset_groups
|
||||
from .models import AssetPermission
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, \
|
||||
AssetGroup, AssetGroupGrantedSerializer, SystemUser, MyAssetGroupGrantedSerializer
|
||||
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
|
||||
from .utils import NodePermissionUtil
|
||||
from .models import NodePermission
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, \
|
||||
NodeGrantedSerializer, SystemUser, NodeSerializer
|
||||
from . import serializers
|
||||
|
||||
|
||||
|
@ -21,102 +18,23 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||
"""
|
||||
资产授权列表的增删改查api
|
||||
"""
|
||||
queryset = AssetPermission.objects.all()
|
||||
serializer_class = serializers.AssetPermissionSerializer
|
||||
queryset = NodePermission.objects.all()
|
||||
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(AssetPermissionViewSet, self).get_queryset()
|
||||
user_id = self.request.query_params.get('user', '')
|
||||
user_group_id = self.request.query_params.get('user_group', '')
|
||||
|
||||
if user_id and user_id.isdigit():
|
||||
user = get_object_or_404(User, id=int(user_id))
|
||||
queryset = get_user_asset_permissions(user)
|
||||
|
||||
if user_group_id:
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
queryset = get_user_group_asset_permissions(user_group)
|
||||
return queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
if getattr(self, 'user_id', ''):
|
||||
return serializers.UserAssetPermissionSerializer
|
||||
return serializers.AssetPermissionSerializer
|
||||
if self.action in ("list", 'retrieve'):
|
||||
return serializers.AssetPermissionListSerializer
|
||||
return self.serializer_class
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
node_id = self.request.query_params.get('node_id')
|
||||
|
||||
class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
|
||||
"""
|
||||
将用户从授权中移除,Detail页面会调用
|
||||
"""
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.AssetPermissionUpdateUserSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
if node_id:
|
||||
queryset = queryset.filter(node__id=node_id)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
perm = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
users = serializer.validated_data.get('users')
|
||||
if users:
|
||||
perm.users.remove(*tuple(users))
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({"error": serializer.errors})
|
||||
|
||||
|
||||
class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.AssetPermissionUpdateUserSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
perm = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
users = serializer.validated_data.get('users')
|
||||
if users:
|
||||
perm.users.add(*tuple(users))
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({"error": serializer.errors})
|
||||
|
||||
|
||||
class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
|
||||
"""
|
||||
将用户从授权中移除,Detail页面会调用
|
||||
"""
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
perm = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
assets = serializer.validated_data.get('assets')
|
||||
if assets:
|
||||
perm.assets.remove(*tuple(assets))
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({"error": serializer.errors})
|
||||
|
||||
|
||||
class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
perm = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
assets = serializer.validated_data.get('assets')
|
||||
if assets:
|
||||
perm.assets.add(*tuple(assets))
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({"error": serializer.errors})
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedAssetsApi(ListAPIView):
|
||||
|
@ -128,41 +46,43 @@ class UserGrantedAssetsApi(ListAPIView):
|
|||
|
||||
def get_queryset(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
|
||||
queryset = []
|
||||
|
||||
if user_id:
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
for k, v in get_user_granted_assets(user).items():
|
||||
k.system_users_granted = v
|
||||
else:
|
||||
user = self.request.user
|
||||
|
||||
for k, v in NodePermissionUtil.get_user_assets(user).items():
|
||||
if k.is_unixlike():
|
||||
system_users_granted = [s for s in v if s.protocol == 'ssh']
|
||||
else:
|
||||
system_users_granted = [s for s in v if s.protocol == 'rdp']
|
||||
k.system_users_granted = system_users_granted
|
||||
queryset.append(k)
|
||||
return queryset
|
||||
|
||||
def get_permissions(self):
|
||||
if self.kwargs.get('pk') is None:
|
||||
self.permission_classes = (IsValidUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
class UserGrantedAssetGroupsApi(APIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
asset_groups = {}
|
||||
user_id = kwargs.get('pk', '')
|
||||
class UserGrantedNodesApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = NodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
if user_id:
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
|
||||
assets = get_user_granted_assets(user)
|
||||
for asset in assets:
|
||||
for asset_group in asset.groups.all():
|
||||
if asset_group.id in asset_groups:
|
||||
asset_groups[asset_group.id]['assets_amount'] += 1
|
||||
else:
|
||||
asset_groups[asset_group.id] = {
|
||||
'id': asset_group.id,
|
||||
'name': asset_group.name,
|
||||
'comment': asset_group.comment,
|
||||
'assets_amount': 1
|
||||
}
|
||||
asset_groups_json = asset_groups.values()
|
||||
return Response(asset_groups_json, status=200)
|
||||
user = self.request.user
|
||||
nodes = NodePermissionUtil.get_user_nodes(user)
|
||||
return nodes.keys()
|
||||
|
||||
|
||||
class UserGrantedAssetGroupsWithAssetsApi(ListAPIView):
|
||||
class UserGrantedNodesWithAssetsApi(ListAPIView):
|
||||
"""
|
||||
授权用户的资产组,注:这里的资产组并非是授权列表中授权的,
|
||||
而是把所有资产取出来,然后反查出所有资产组,然后合并得到,
|
||||
|
@ -171,7 +91,7 @@ class UserGrantedAssetGroupsWithAssetsApi(ListAPIView):
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "资产组1",
|
||||
"value": "node",
|
||||
... 其它属性
|
||||
"assets_granted": [
|
||||
{
|
||||
|
@ -191,134 +111,36 @@ class UserGrantedAssetGroupsWithAssetsApi(ListAPIView):
|
|||
]
|
||||
"""
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
serializer_class = AssetGroupGrantedSerializer
|
||||
serializer_class = NodeGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
queryset = []
|
||||
if not user_id:
|
||||
return []
|
||||
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
asset_groups = get_user_granted_asset_groups(user)
|
||||
|
||||
queryset = []
|
||||
for asset_group, assets_system_users in asset_groups.items():
|
||||
assets = []
|
||||
for asset, system_users in assets_system_users:
|
||||
asset.system_users_granted = system_users
|
||||
assets.append(asset)
|
||||
asset_group.assets_granted = assets
|
||||
queryset.append(asset_group)
|
||||
return queryset
|
||||
|
||||
|
||||
class MyGrantedAssetsApi(ListAPIView):
|
||||
"""
|
||||
用户自己查询授权的资产列表
|
||||
"""
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
user = self.request.user
|
||||
if user:
|
||||
for asset, system_users in get_user_granted_assets(user).items():
|
||||
asset.system_users_granted = system_users
|
||||
queryset.append(asset)
|
||||
return queryset
|
||||
|
||||
|
||||
class MyGrantedAssetGroupsApi(APIView):
|
||||
"""
|
||||
授权的所有资产组,并非是授权列表中的,而是经过计算得来的
|
||||
"""
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
asset_groups = {}
|
||||
user = request.user
|
||||
|
||||
if user:
|
||||
assets = get_user_granted_assets(user)
|
||||
for asset in assets:
|
||||
for asset_group in asset.groups.all():
|
||||
if asset_group.id in asset_groups:
|
||||
asset_groups[asset_group.id]['assets_amount'] += 1
|
||||
else:
|
||||
asset_groups[asset_group.id] = {
|
||||
'id': asset_group.id,
|
||||
'name': asset_group.name,
|
||||
'comment': asset_group.comment,
|
||||
'assets_amount': 1
|
||||
}
|
||||
asset_groups_json = asset_groups.values()
|
||||
return Response(asset_groups_json, status=200)
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
|
||||
|
||||
class MyGrantedAssetGroupsWithAssetsApi(ListAPIView):
|
||||
"""
|
||||
授权当前用户的资产组,注:这里的资产组并非是授权列表中授权的,
|
||||
而是把所有资产取出来,然后反查出所有资产组,然后合并得到,
|
||||
结果里也包含资产组下授权的资产
|
||||
数据结构如下:
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "资产组1",
|
||||
... 其它属性
|
||||
"assets_granted": [
|
||||
{
|
||||
"id": 1,
|
||||
"hostname": "testserver",
|
||||
"system_users_granted": [
|
||||
"id": 1,
|
||||
"name": "web",
|
||||
"username": "web",
|
||||
"protocol": "ssh",
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"""
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = MyAssetGroupGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
asset_groups = get_user_granted_asset_groups(user)
|
||||
|
||||
queryset = []
|
||||
for asset_group, assets_system_users in asset_groups.items():
|
||||
assets = []
|
||||
for asset, system_users in assets_system_users:
|
||||
asset.system_users_granted = system_users
|
||||
assets.append(asset)
|
||||
asset_group.assets_granted = assets
|
||||
queryset.append(asset_group)
|
||||
return queryset
|
||||
|
||||
|
||||
class MyAssetGroupOfAssetsApi(ListAPIView):
|
||||
"""授权用户资产组下的资产列表, 非该资产组的所有资产,而是被授权的"""
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
asset_group_id = self.kwargs.get('pk', -1)
|
||||
user = self.request.user
|
||||
asset_group = get_object_or_none(AssetGroup, id=asset_group_id)
|
||||
|
||||
if user and asset_group:
|
||||
assets = get_user_granted_assets(user)
|
||||
for asset in asset_group.assets.all():
|
||||
if asset in assets:
|
||||
nodes = NodePermissionUtil.get_user_nodes_with_assets(user)
|
||||
assets = {}
|
||||
for k, v in NodePermissionUtil.get_user_assets(user).items():
|
||||
if k.is_unixlike():
|
||||
system_users_granted = [s for s in v if s.protocol == 'ssh']
|
||||
else:
|
||||
system_users_granted = [s for s in v if s.protocol == 'rdp']
|
||||
assets[k] = system_users_granted
|
||||
for node, v in nodes.items():
|
||||
for asset in v['assets']:
|
||||
asset.system_users_granted = assets[asset]
|
||||
queryset.append(asset)
|
||||
node.assets_granted = v['assets']
|
||||
queryset.append(node)
|
||||
return queryset
|
||||
|
||||
def get_permissions(self):
|
||||
if self.kwargs.get('pk') is None:
|
||||
self.permission_classes = (IsValidUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class UserGroupGrantedAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
@ -326,27 +148,52 @@ class UserGroupGrantedAssetsApi(ListAPIView):
|
|||
|
||||
def get_queryset(self):
|
||||
user_group_id = self.kwargs.get('pk', '')
|
||||
|
||||
if user_group_id:
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
queryset = get_user_group_granted_assets(user_group)
|
||||
else:
|
||||
queryset = []
|
||||
|
||||
if not user_group_id:
|
||||
return queryset
|
||||
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
assets = NodePermissionUtil.get_user_group_assets(user_group)
|
||||
for k, v in assets.items():
|
||||
k.system_users_granted = v
|
||||
queryset.append(k)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGroupGrantedAssetGroupsApi(ListAPIView):
|
||||
class UserGroupGrantedNodesApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = AssetGroupGrantedSerializer
|
||||
serializer_class = NodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
group_id = self.kwargs.get('pk', '')
|
||||
queryset = []
|
||||
|
||||
if group_id:
|
||||
group = get_object_or_404(UserGroup, id=group_id)
|
||||
nodes = NodePermissionUtil.get_user_group_nodes(group)
|
||||
queryset = nodes.keys()
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = NodeGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_group_id = self.kwargs.get('pk', '')
|
||||
|
||||
if user_group_id:
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
queryset = get_user_group_granted_asset_groups(user_group)
|
||||
else:
|
||||
queryset = []
|
||||
|
||||
if not user_group_id:
|
||||
return queryset
|
||||
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
nodes = NodePermissionUtil.get_user_group_nodes_with_assets(user_group)
|
||||
for node, v in nodes.items():
|
||||
for asset in v['assets']:
|
||||
asset.system_users_granted = v['system_users']
|
||||
node.assets_granted = v['assets']
|
||||
queryset.append(node)
|
||||
return queryset
|
||||
|
||||
|
||||
|
@ -363,7 +210,7 @@ class ValidateUserAssetPermissionView(APIView):
|
|||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_id)
|
||||
|
||||
assets_granted = get_user_granted_assets(user)
|
||||
assets_granted = NodePermissionUtil.get_user_assets(user)
|
||||
if system_user in assets_granted.get(asset, []):
|
||||
return Response({'msg': True}, status=200)
|
||||
else:
|
||||
|
|
|
@ -5,3 +5,7 @@ from django.apps import AppConfig
|
|||
|
||||
class PermsConfig(AppConfig):
|
||||
name = 'perms'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
return super().ready()
|
||||
|
|
|
@ -4,94 +4,27 @@ from __future__ import absolute_import, unicode_literals
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# from .hands import User, UserGroup, Asset, AssetGroup, SystemUser
|
||||
from .models import AssetPermission
|
||||
from users.models import User
|
||||
from .models import NodePermission
|
||||
|
||||
|
||||
class AssetPermissionForm(forms.ModelForm):
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.exclude(role=User.ROLE_APP),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select users')},
|
||||
),
|
||||
label=_("User"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
model = NodePermission
|
||||
fields = [
|
||||
'name', 'users', 'user_groups', 'assets', 'asset_groups',
|
||||
'system_users', 'is_active', 'date_expired', 'comment',
|
||||
'node', 'user_group', 'system_user', 'is_active',
|
||||
'date_expired', 'comment',
|
||||
]
|
||||
widgets = {
|
||||
'user_groups': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select user groups')}),
|
||||
'assets': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select assets')}),
|
||||
'asset_groups': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset groups')}),
|
||||
'system_users': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select system users')}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'system_users': '* required',
|
||||
'node': forms.Select(
|
||||
attrs={'style': 'display:none'}
|
||||
),
|
||||
'user_group': forms.Select(
|
||||
attrs={'class': 'select2', 'data-placeholder': _("User group")}
|
||||
),
|
||||
'system_user': forms.Select(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('System user')}
|
||||
),
|
||||
}
|
||||
|
||||
def clean_user_groups(self):
|
||||
users = self.cleaned_data.get('users')
|
||||
user_groups = self.cleaned_data.get('user_groups')
|
||||
|
||||
if not users and not user_groups:
|
||||
raise forms.ValidationError(_("User or group at least one required"))
|
||||
return self.cleaned_data["user_groups"]
|
||||
|
||||
def clean_asset_groups(self):
|
||||
assets = self.cleaned_data.get('assets')
|
||||
asset_groups = self.cleaned_data.get('asset_groups')
|
||||
|
||||
if not assets and not asset_groups:
|
||||
raise forms.ValidationError(_("Asset or group at least one required"))
|
||||
|
||||
return self.cleaned_data["asset_groups"]
|
||||
|
||||
def clean_system_users(self):
|
||||
from assets.utils import check_assets_have_system_user
|
||||
|
||||
errors = []
|
||||
assets = self.cleaned_data['assets']
|
||||
asset_groups = self.cleaned_data.get('asset_groups')
|
||||
system_users = self.cleaned_data.get('system_users')
|
||||
|
||||
if not asset_groups and not assets:
|
||||
return self.cleaned_data.get("system_users")
|
||||
|
||||
error_data = check_assets_have_system_user(assets, system_users)
|
||||
if error_data:
|
||||
for asset, system_users in error_data.items():
|
||||
msg = _("Asset {} of cluster {} not have [{}] system users, please check \n")
|
||||
error = forms.ValidationError(msg.format(
|
||||
asset.hostname,
|
||||
asset.cluster.name,
|
||||
", ".join(system_user.name for system_user in system_users)
|
||||
))
|
||||
errors.append(error)
|
||||
|
||||
for group in asset_groups:
|
||||
msg = _("Asset {}(group {}) of cluster {} not have [{}] system users, please check \n")
|
||||
assets = group.assets.all()
|
||||
error_data = check_assets_have_system_user(assets, system_users)
|
||||
for asset, system_users in error_data.items():
|
||||
errors.append(msg.format(
|
||||
asset.hostname, group.name, asset.cluster.name,
|
||||
", ".join(system_user.name for system_user in system_users)
|
||||
))
|
||||
if errors:
|
||||
raise forms.ValidationError(errors)
|
||||
return self.cleaned_data['system_users']
|
||||
def clean_system_user(self):
|
||||
return self.cleaned_data['system_user']
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, AssetGroup, SystemUser
|
||||
from assets.serializers import AssetGrantedSerializer, AssetGroupGrantedSerializer, MyAssetGroupGrantedSerializer
|
||||
from assets.models import Asset, AssetGroup, SystemUser, Node
|
||||
from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -67,3 +67,22 @@ class AssetPermission(models.Model):
|
|||
if cluster_remain:
|
||||
errors[system_user] = cluster_remain
|
||||
return errors
|
||||
|
||||
|
||||
class NodePermission(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
node = models.ForeignKey('assets.Node', on_delete=models.CASCADE, verbose_name=_("Node"))
|
||||
user_group = models.ForeignKey('users.UserGroup', on_delete=models.CASCADE, verbose_name=_("User group"))
|
||||
system_user = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, verbose_name=_("System user"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
||||
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
|
||||
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{}:{}:{}".format(self.node.value, self.user_group.name, self.system_user.name)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('node', 'user_group', 'system_user')
|
||||
verbose_name = _("Asset permission")
|
||||
|
|
|
@ -4,41 +4,28 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from common.utils import get_object_or_none
|
||||
from .models import AssetPermission
|
||||
from .hands import User
|
||||
from common.fields import StringIDField
|
||||
from .models import AssetPermission, NodePermission
|
||||
|
||||
|
||||
class AssetPermissionSerializer(serializers.ModelSerializer):
|
||||
assets_ = serializers.SerializerMethodField()
|
||||
asset_groups_ = serializers.SerializerMethodField()
|
||||
users_ = serializers.SerializerMethodField()
|
||||
user_groups_ = serializers.SerializerMethodField()
|
||||
system_users_ = serializers.SerializerMethodField()
|
||||
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NodePermission
|
||||
fields = [
|
||||
'id', 'node', 'user_group', 'system_user',
|
||||
'is_active', 'date_expired'
|
||||
]
|
||||
|
||||
|
||||
class AssetPermissionListSerializer(serializers.ModelSerializer):
|
||||
node = StringIDField(read_only=True)
|
||||
user_group = StringIDField(read_only=True)
|
||||
system_user = StringIDField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
model = NodePermission
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_assets_(obj):
|
||||
return [asset.hostname for asset in obj.assets.all()]
|
||||
|
||||
@staticmethod
|
||||
def get_asset_groups_(obj):
|
||||
return [group.name for group in obj.asset_groups.all()]
|
||||
|
||||
@staticmethod
|
||||
def get_users_(obj):
|
||||
return [user.username for user in obj.users.all()]
|
||||
|
||||
@staticmethod
|
||||
def get_user_groups_(obj):
|
||||
return [group.name for group in obj.user_groups.all()]
|
||||
|
||||
@staticmethod
|
||||
def get_system_users_(obj):
|
||||
return [user.username for user in obj.system_users.all()]
|
||||
|
||||
|
||||
class AssetPermissionUpdateUserSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
@ -54,7 +41,7 @@ class AssetPermissionUpdateAssetSerializer(serializers.ModelSerializer):
|
|||
fields = ['id', 'assets']
|
||||
|
||||
|
||||
class UserAssetPermissionSerializer(AssetPermissionSerializer):
|
||||
class UserAssetPermissionCreateUpdateSerializer(AssetPermissionCreateUpdateSerializer):
|
||||
is_inherited = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import NodePermission
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@receiver(post_save, sender=NodePermission, dispatch_uid="my_unique_identifier")
|
||||
def on_asset_permission_create_or_update(sender, instance=None, **kwargs):
|
||||
if instance and instance.node and instance.system_user:
|
||||
instance.system_user.nodes.add(instance.node)
|
||||
|
|
@ -7,7 +7,4 @@ from common.utils import get_logger, encrypt_password
|
|||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@shared_task(bind=True)
|
||||
def push_users(self, assets, users):
|
||||
pass
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ function updateGroup(groups) {
|
|||
}
|
||||
|
||||
jumpserver.assets_selected = {};
|
||||
jumpserver.groups_selected = {};
|
||||
jumpserver.nodes_selected = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2.asset').select2()
|
||||
|
@ -206,11 +206,11 @@ $(document).ready(function () {
|
|||
$('.select2.group').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.groups_selected[data.id] = data.text;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.groups_selected[data.id]
|
||||
delete jumpserver.nodes_selected[data.id]
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-add-assets', function () {
|
||||
|
@ -232,7 +232,7 @@ $(document).ready(function () {
|
|||
removeAssets(assets)
|
||||
})
|
||||
.on('click', '#btn-add-group', function () {
|
||||
if (Object.keys(jumpserver.groups_selected).length === 0) {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,7 @@ $(document).ready(function () {
|
|||
return $(this).data('gid');
|
||||
}).get();
|
||||
|
||||
$.map(jumpserver.groups_selected, function(group_name, index) {
|
||||
$.map(jumpserver.nodes_selected, function(group_name, index) {
|
||||
groups.push(index);
|
||||
$('#opt_' + index).remove();
|
||||
$('.group_edit tbody').append(
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create asset permission ' %}</h5>
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
@ -28,19 +28,23 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" class="form-horizontal" action="" >
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Name' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'User' %}</h3>
|
||||
{% bootstrap_field form.users layout="horizontal" %}
|
||||
{% bootstrap_field form.user_groups layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Asset' %}</h3>
|
||||
{% bootstrap_field form.assets layout="horizontal" %}
|
||||
{% bootstrap_field form.asset_groups layout="horizontal" %}
|
||||
{% bootstrap_field form.system_users layout="horizontal" %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="id_name">{% trans 'Node' %}</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control" readonly value="{{ form.node.initial }}">
|
||||
</div>
|
||||
</div>
|
||||
{{ form.node }}
|
||||
{% bootstrap_field form.user_group layout="horizontal" %}
|
||||
{% bootstrap_field form.system_user layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
<div class="form-group">
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue