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]
|
1. [步骤1]
|
||||||
|
|
|
@ -29,4 +29,5 @@ media
|
||||||
celerybeat.pid
|
celerybeat.pid
|
||||||
django.db
|
django.db
|
||||||
celerybeat-schedule.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/)
|
[![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/)
|
[![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 功能
|
### Feature 功能
|
||||||
- Auth 统一认证
|
- Auth 统一认证
|
||||||
|
@ -24,9 +32,16 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互
|
||||||
* Python 3.6
|
* Python 3.6
|
||||||
* Django 1.11
|
* 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 使用
|
### Usage 使用
|
||||||
|
@ -70,6 +85,11 @@ demo使用了开发者模式,并发只能为1
|
||||||
|
|
||||||
参见 https://github.com/jumpserver/jumpserver/milestone/2
|
参见 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 开发者文档
|
### 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.
|
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.
|
:license: GPL v2, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from users.utils import AdminUserRequiredMixin
|
from common.mixins import AdminUserRequiredMixin
|
||||||
from users.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||||
from users.models import User, UserGroup
|
from users.models import User, UserGroup
|
||||||
from perms.utils import get_user_granted_assets
|
from perms.utils import NodePermissionUtil
|
||||||
from perms.tasks import push_users
|
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from .user import AdminUser, SystemUser
|
from .user import AdminUser, SystemUser
|
||||||
|
from .label import Label
|
||||||
from .cluster import *
|
from .cluster import *
|
||||||
from .group import *
|
from .group import *
|
||||||
|
from .node import *
|
||||||
from .asset import *
|
from .asset import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
|
@ -28,46 +28,36 @@ def default_cluster():
|
||||||
return cluster.id
|
return cluster.id
|
||||||
|
|
||||||
|
|
||||||
class Asset(models.Model):
|
def default_node():
|
||||||
# Todo: Move them to settings
|
try:
|
||||||
STATUS_CHOICES = (
|
from .node import Node
|
||||||
('In use', _('In use')),
|
return Node.root()
|
||||||
('Out of use', _('Out of use')),
|
except:
|
||||||
)
|
return None
|
||||||
TYPE_CHOICES = (
|
|
||||||
('Server', _('Server')),
|
|
||||||
('VM', _('VM')),
|
|
||||||
('Switch', _('Switch')),
|
|
||||||
('Router', _('Router')),
|
|
||||||
('Firewall', _('Firewall')),
|
|
||||||
('Storage', _("Storage")),
|
|
||||||
)
|
|
||||||
ENV_CHOICES = (
|
|
||||||
('Prod', _('Production')),
|
|
||||||
('Dev', _('Development')),
|
|
||||||
('Test', _('Testing')),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
class Asset(models.Model):
|
||||||
# Important
|
# Important
|
||||||
|
PLATFORM_CHOICES = (
|
||||||
|
('Linux', 'Linux'),
|
||||||
|
('Unix', 'Unix'),
|
||||||
|
('MacOS', 'MacOS'),
|
||||||
|
('BSD', 'BSD'),
|
||||||
|
('Windows', 'Windows'),
|
||||||
|
('Other', 'Other'),
|
||||||
|
)
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
|
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||||
cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster'))
|
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
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
|
# 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
|
# Some information
|
||||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
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'))
|
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||||
|
|
||||||
# Collect
|
# 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_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'))
|
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 = 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_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'))
|
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'))
|
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'))
|
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'))
|
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'))
|
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
@ -104,6 +95,12 @@ class Asset(models.Model):
|
||||||
return True, ''
|
return True, ''
|
||||||
return False, warning
|
return False, warning
|
||||||
|
|
||||||
|
def is_unixlike(self):
|
||||||
|
if self.platform not in ("Windows", "Other"):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hardware_info(self):
|
def hardware_info(self):
|
||||||
if self.cpu_count:
|
if self.cpu_count:
|
||||||
|
@ -116,36 +113,20 @@ class Asset(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_connective(self):
|
def is_connective(self):
|
||||||
|
if not self.is_unixlike():
|
||||||
|
return True
|
||||||
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
||||||
if val == 1:
|
if val == 1:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
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):
|
def to_json(self):
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'hostname': self.hostname,
|
'hostname': self.hostname,
|
||||||
'ip': self.ip,
|
'ip': self.ip,
|
||||||
'port': self.port,
|
'port': self.port,
|
||||||
'groups': [group.name for group in self.groups.all()],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _to_secret_json(self):
|
def _to_secret_json(self):
|
||||||
|
@ -156,13 +137,14 @@ class Asset(models.Model):
|
||||||
Todo: May be move to ops implements it
|
Todo: May be move to ops implements it
|
||||||
"""
|
"""
|
||||||
data = self.to_json()
|
data = self.to_json()
|
||||||
if self.admin_user_avail:
|
if self.admin_user:
|
||||||
admin_user = self.admin_user_avail
|
admin_user = self.admin_user
|
||||||
data.update({
|
data.update({
|
||||||
'username': admin_user.username,
|
'username': admin_user.username,
|
||||||
'password': admin_user.password,
|
'password': admin_user.password,
|
||||||
'private_key': admin_user.private_key_file,
|
'private_key': admin_user.private_key_file,
|
||||||
'become': admin_user.become_info,
|
'become': admin_user.become_info,
|
||||||
|
'groups': [node.value for node in self.nodes.all()],
|
||||||
})
|
})
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -181,7 +163,6 @@ class Asset(models.Model):
|
||||||
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
|
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
|
||||||
hostname=forgery_py.internet.user_name(True),
|
hostname=forgery_py.internet.user_name(True),
|
||||||
admin_user=choice(AdminUser.objects.all()),
|
admin_user=choice(AdminUser.objects.all()),
|
||||||
cluster=choice(Cluster.objects.all()),
|
|
||||||
port=22,
|
port=22,
|
||||||
created_by='Fake')
|
created_by='Fake')
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
|
||||||
class AssetGroup(models.Model):
|
class AssetGroup(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
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'))
|
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
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):
|
class AssetUser(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
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'))
|
_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, ])
|
_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'))
|
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
date_updated = models.DateTimeField(auto_now=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
|
@property
|
||||||
def password(self):
|
def password(self):
|
||||||
|
@ -175,15 +175,12 @@ class AdminUser(AssetUser):
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def get_related_assets(self):
|
def get_related_assets(self):
|
||||||
assets = []
|
assets = self.asset_set.all()
|
||||||
for cluster in self.cluster_set.all():
|
return assets
|
||||||
assets.extend(cluster.assets.all())
|
|
||||||
assets.extend(self.asset_set.all())
|
|
||||||
return list(set(assets))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def assets_amount(self):
|
def assets_amount(self):
|
||||||
return len(self.get_related_assets())
|
return self.get_related_assets().count()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
@ -212,11 +209,13 @@ class AdminUser(AssetUser):
|
||||||
|
|
||||||
class SystemUser(AssetUser):
|
class SystemUser(AssetUser):
|
||||||
SSH_PROTOCOL = 'ssh'
|
SSH_PROTOCOL = 'ssh'
|
||||||
|
RDP_PROTOCOL = 'rdp'
|
||||||
PROTOCOL_CHOICES = (
|
PROTOCOL_CHOICES = (
|
||||||
(SSH_PROTOCOL, 'ssh'),
|
(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"))
|
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
|
||||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||||
|
@ -226,21 +225,6 @@ class SystemUser(AssetUser):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
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):
|
def to_json(self):
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
|
@ -251,6 +235,13 @@ class SystemUser(AssetUser):
|
||||||
'auto_push': self.auto_push,
|
'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
|
@property
|
||||||
def assets_connective(self):
|
def assets_connective(self):
|
||||||
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
|
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
|
||||||
|
@ -264,6 +255,12 @@ class SystemUser(AssetUser):
|
||||||
def reachable_assets(self):
|
def reachable_assets(self):
|
||||||
return self.assets_connective.get('contacted', [])
|
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:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
verbose_name = _("System user")
|
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
|
from django.dispatch import Signal
|
||||||
|
|
||||||
on_app_ready = Signal()
|
on_app_ready = Signal()
|
||||||
on_system_user_auth_changed = Signal(providing_args=['system_user'])
|
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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.dispatch import receiver
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
|
|
||||||
from common.utils import get_logger
|
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, \
|
from .tasks import update_assets_hardware_info_util, \
|
||||||
test_asset_connectability_util, \
|
test_asset_connectability_util, push_system_user_to_node, \
|
||||||
push_system_user_util
|
push_node_system_users_to_asset
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
@ -25,105 +24,48 @@ def test_asset_conn_on_created(asset):
|
||||||
test_asset_connectability_util.delay(asset)
|
test_asset_connectability_util.delay(asset)
|
||||||
|
|
||||||
|
|
||||||
def push_cluster_system_users_to_asset(asset):
|
def set_asset_root_node(asset):
|
||||||
if asset.cluster:
|
logger.debug("Set asset default node: {}".format(Node.root()))
|
||||||
logger.info("Push cluster system user to asset: {}".format(asset))
|
asset.nodes.add(Node.root())
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||||
def on_asset_created(sender, instance=None, created=False, **kwargs):
|
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||||
if instance and created:
|
set_asset_root_node(instance)
|
||||||
|
if created:
|
||||||
logger.info("Asset `{}` create signal received".format(instance))
|
logger.info("Asset `{}` create signal received".format(instance))
|
||||||
update_asset_hardware_info_on_created(instance)
|
update_asset_hardware_info_on_created(instance)
|
||||||
test_asset_conn_on_created(instance)
|
test_asset_conn_on_created(instance)
|
||||||
push_cluster_system_users_to_asset(instance)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_init, sender=Asset)
|
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
||||||
def on_asset_init(sender, instance, created=False, **kwargs):
|
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
||||||
if instance and created is False:
|
if instance and not created:
|
||||||
instance.__original_cluster = instance.cluster
|
for node in instance.nodes.all():
|
||||||
|
push_system_user_to_node(instance, node)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Asset)
|
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
||||||
def on_asset_cluster_changed(sender, instance=None, created=False, **kwargs):
|
def on_system_user_node_change(sender, instance=None, **kwargs):
|
||||||
if instance and created is False and instance.cluster != instance.__original_cluster:
|
if instance and kwargs["action"] == "post_add":
|
||||||
logger.info("Asset cluster changed signal received")
|
for pk in kwargs['pk_set']:
|
||||||
push_cluster_system_users_to_asset(instance)
|
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):
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
if not system_user.auto_push:
|
def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||||
return
|
if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
|
||||||
logger.debug("Push system user `{}` to cluster assets".format(system_user.name))
|
logger.debug("Asset node change signal received")
|
||||||
for cluster in system_user.cluster.all():
|
for pk in kwargs['pk_set']:
|
||||||
task_name = _("Push system user to cluster assets: {}->{}").format(
|
node = kwargs['model'].objects.get(pk=pk)
|
||||||
cluster.name, system_user.name
|
push_node_system_users_to_asset(node, [instance])
|
||||||
)
|
|
||||||
assets = cluster.assets.all()
|
|
||||||
push_system_user_util.delay([system_user], assets, task_name)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=SystemUser)
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
def on_system_user_created_or_updated(sender, instance=None, **kwargs):
|
def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||||
if instance and instance.auto_push:
|
if isinstance(instance, Node) and kwargs['action'] == 'post_add':
|
||||||
logger.info("System user `{}` create or update signal received".format(instance))
|
logger.debug("Node assets change signal received")
|
||||||
push_to_cluster_assets_on_system_user_created_or_update(instance)
|
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
|
push_node_system_users_to_asset(instance, assets)
|
||||||
|
|
||||||
@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)
|
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ FORKS = 10
|
||||||
TIMEOUT = 60
|
TIMEOUT = 60
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
CACHE_MAX_TIME = 60*60*60
|
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
|
@shared_task
|
||||||
|
@ -36,39 +36,40 @@ def set_assets_hardware_info(result, **kwargs):
|
||||||
result_raw = result[0]
|
result_raw = result[0]
|
||||||
assets_updated = []
|
assets_updated = []
|
||||||
for hostname, info in result_raw.get('ok', {}).items():
|
for hostname, info in result_raw.get('ok', {}).items():
|
||||||
if info:
|
info = info.get('setup', {}).get('ansible_facts', {})
|
||||||
info = info['setup']['ansible_facts']
|
if not info:
|
||||||
else:
|
logger.error("Get asset info failed: {}".format(hostname))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
asset = get_object_or_none(Asset, hostname=hostname)
|
asset = get_object_or_none(Asset, hostname=hostname)
|
||||||
if not asset:
|
if not asset:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
___vendor = info['ansible_system_vendor']
|
___vendor = info.get('ansible_system_vendor', 'Unknown')
|
||||||
___model = info['ansible_product_version']
|
___model = info.get('ansible_product_name', 'Unknown')
|
||||||
___sn = info['ansible_product_serial']
|
___sn = info.get('ansible_product_serial', 'Unknown')
|
||||||
|
|
||||||
for ___cpu_model in info['ansible_processor']:
|
for ___cpu_model in info.get('ansible_processor', []):
|
||||||
if ___cpu_model.endswith('GHz'):
|
if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
___cpu_model = 'Unknown'
|
___cpu_model = 'Unknown'
|
||||||
___cpu_count = info['ansible_processor_count']
|
___cpu_model = ___cpu_model[:64]
|
||||||
___cpu_cores = info['ansible_processor_cores']
|
___cpu_count = info.get('ansible_processor_count', 0)
|
||||||
___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb']))
|
___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 = {}
|
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':
|
if disk_pattern.match(dev) and dev_info['removable'] == '0':
|
||||||
disk_info[dev] = dev_info['size']
|
disk_info[dev] = dev_info['size']
|
||||||
___disk_total = '%s %s' % sum_capacity(disk_info.values())
|
___disk_total = '%s %s' % sum_capacity(disk_info.values())
|
||||||
___disk_info = json.dumps(disk_info)
|
___disk_info = json.dumps(disk_info)
|
||||||
|
|
||||||
___platform = info['ansible_system']
|
___platform = info.get('ansible_system', 'Unknown')
|
||||||
___os = info['ansible_distribution']
|
___os = info.get('ansible_distribution', 'Unknown')
|
||||||
___os_version = info['ansible_distribution_version']
|
___os_version = info.get('ansible_distribution_version', 'Unknown')
|
||||||
___os_arch = info['ansible_architecture']
|
___os_arch = info.get('ansible_architecture', 'Unknown')
|
||||||
___hostname_raw = info['ansible_hostname']
|
___hostname_raw = info.get('ansible_hostname', 'Unknown')
|
||||||
|
|
||||||
for k, v in locals().items():
|
for k, v in locals().items():
|
||||||
if k.startswith('___'):
|
if k.startswith('___'):
|
||||||
|
@ -90,7 +91,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||||
if task_name is None:
|
if task_name is None:
|
||||||
task_name = _("Update some assets hardware info")
|
task_name = _("Update some assets hardware info")
|
||||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
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, created = update_or_create_ansible_task(
|
||||||
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
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()
|
result = task.run()
|
||||||
# Todo: may be somewhere using
|
# Todo: may be somewhere using
|
||||||
# Manual run callback function
|
# Manual run callback function
|
||||||
assets_updated = set_assets_hardware_info(result)
|
set_assets_hardware_info(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,7 +120,10 @@ def update_assets_hardware_info_period():
|
||||||
"""
|
"""
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
task_name = _("Update assets hardware info period")
|
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
|
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||||
|
|
||||||
# Only create, schedule by celery beat
|
# 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
|
from ops.utils import update_or_create_ansible_task
|
||||||
|
|
||||||
assets = admin_user.get_related_assets()
|
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
|
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||||
task, created = update_or_create_ansible_task(
|
task, created = update_or_create_ansible_task(
|
||||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
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
|
A period task that update the ansible task period
|
||||||
"""
|
"""
|
||||||
from ops.utils import update_or_create_ansible_task
|
|
||||||
admin_users = AdminUser.objects.all()
|
admin_users = AdminUser.objects.all()
|
||||||
for admin_user in admin_users:
|
for admin_user in admin_users:
|
||||||
task_name = _("Test admin user connectability period: {}").format(admin_user)
|
task_name = _("Test admin user connectability period: {}".format(admin_user.name))
|
||||||
assets = admin_user.get_related_assets()
|
test_admin_user_connectability_util(admin_user, task_name)
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
@ -262,8 +260,8 @@ def test_system_user_connectability_util(system_user, task_name):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
assets = system_user.get_clusters_assets()
|
assets = system_user.assets
|
||||||
hosts = [asset.hostname for asset in assets]
|
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||||
if not hosts:
|
if not hosts:
|
||||||
logger.info("No hosts, passed")
|
logger.info("No hosts, passed")
|
||||||
|
@ -289,21 +287,10 @@ def test_system_user_connectability_manual(system_user):
|
||||||
@after_app_ready_start
|
@after_app_ready_start
|
||||||
@after_app_shutdown_clean
|
@after_app_shutdown_clean
|
||||||
def test_system_user_connectability_period():
|
def test_system_user_connectability_period():
|
||||||
from ops.utils import update_or_create_ansible_task
|
|
||||||
system_users = SystemUser.objects.all()
|
system_users = SystemUser.objects.all()
|
||||||
for system_user in system_users:
|
for system_user in system_users:
|
||||||
task_name = _("Test system user connectability period: {}").format(
|
task_name = _("test system user connectability period: {}".format(system_user))
|
||||||
system_user.name
|
test_system_user_connectability_util(system_user, task_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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#### Push system user tasks ####
|
#### Push system user tasks ####
|
||||||
|
@ -313,8 +300,9 @@ def get_push_system_user_tasks(system_user):
|
||||||
if system_user.username == "root":
|
if system_user.username == "root":
|
||||||
return []
|
return []
|
||||||
|
|
||||||
tasks = [
|
tasks = []
|
||||||
{
|
if system_user.password:
|
||||||
|
tasks.append({
|
||||||
'name': 'Add user {}'.format(system_user.username),
|
'name': 'Add user {}'.format(system_user.username),
|
||||||
'action': {
|
'action': {
|
||||||
'module': 'user',
|
'module': 'user',
|
||||||
|
@ -323,8 +311,9 @@ def get_push_system_user_tasks(system_user):
|
||||||
encrypt_password(system_user.password, salt="K3mIlKK"),
|
encrypt_password(system_user.password, salt="K3mIlKK"),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
{
|
if system_user.public_key:
|
||||||
|
tasks.append({
|
||||||
'name': 'Set {} authorized key'.format(system_user.username),
|
'name': 'Set {} authorized key'.format(system_user.username),
|
||||||
'action': {
|
'action': {
|
||||||
'module': 'authorized_key',
|
'module': 'authorized_key',
|
||||||
|
@ -332,8 +321,9 @@ def get_push_system_user_tasks(system_user):
|
||||||
system_user.username, system_user.public_key
|
system_user.username, system_user.public_key
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
{
|
if system_user.sudo:
|
||||||
|
tasks.append({
|
||||||
'name': 'Set {} sudo setting'.format(system_user.username),
|
'name': 'Set {} sudo setting'.format(system_user.username),
|
||||||
'action': {
|
'action': {
|
||||||
'module': 'lineinfile',
|
'module': 'lineinfile',
|
||||||
|
@ -344,8 +334,7 @@ def get_push_system_user_tasks(system_user):
|
||||||
system_user.sudo,
|
system_user.sudo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
]
|
|
||||||
return tasks
|
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
|
from ops.utils import update_or_create_ansible_task
|
||||||
tasks = []
|
tasks = []
|
||||||
for system_user in system_users:
|
for system_user in system_users:
|
||||||
tasks.extend(get_push_system_user_tasks(system_user))
|
if system_user.is_need_push():
|
||||||
|
tasks.extend(get_push_system_user_tasks(system_user))
|
||||||
|
|
||||||
if not tasks:
|
if not tasks:
|
||||||
logger.info("Not tasks, passed")
|
logger.info("Not tasks, passed")
|
||||||
return {}
|
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:
|
if not hosts:
|
||||||
logger.info("Not hosts, passed")
|
logger.info("Not hosts, passed")
|
||||||
return {}
|
return {}
|
||||||
|
@ -371,35 +361,47 @@ def push_system_user_util(system_users, assets, task_name):
|
||||||
return task.run()
|
return task.run()
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
def get_node_push_system_user_task_name(system_user, node):
|
||||||
def push_system_user_to_cluster_assets_manual(system_user):
|
return _("Push system user to node: {} => {}").format(
|
||||||
task_name = _("Push system user to cluster assets: {}").format(system_user.name)
|
system_user.name,
|
||||||
assets = system_user.get_clusters_assets()
|
node.value
|
||||||
return push_system_user_util([system_user], assets, task_name)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
@shared_task
|
||||||
@register_as_period_task(interval=3600)
|
def push_system_user_related_nodes(system_user):
|
||||||
@after_app_ready_start
|
nodes = system_user.nodes.all()
|
||||||
@after_app_shutdown_clean
|
for node in nodes:
|
||||||
def push_system_user_period():
|
push_system_user_to_node(system_user, node)
|
||||||
from ops.utils import update_or_create_ansible_task
|
|
||||||
clusters = Cluster.objects.all()
|
|
||||||
|
|
||||||
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(
|
@shared_task
|
||||||
cluster.name
|
def push_system_user_to_assets_manual(system_user):
|
||||||
)
|
push_system_user_related_nodes(system_user)
|
||||||
hosts = [asset.hostname for asset in cluster.assets.all()]
|
|
||||||
update_or_create_ansible_task(
|
|
||||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
def push_node_system_users_to_asset(node, assets):
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
system_users = []
|
||||||
interval=60*60*24, is_periodic=True,
|
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 %}
|
{% load bootstrap3 %}
|
||||||
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
|
<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">
|
<form method="post" class="form-horizontal" action="" id="fm_asset_group_bulk_update">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
|
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
|
||||||
<div class="col-sm-9" id="select2-container">
|
<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="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'Create system user' %}</h5>
|
<h5>{{ action }}</h5>
|
||||||
<div class="ibox-tools">
|
<div class="ibox-tools">
|
||||||
<a class="collapse-link">
|
<a class="collapse-link">
|
||||||
<i class="fa fa-chevron-up"></i>
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
@ -39,7 +39,6 @@
|
||||||
{% bootstrap_field form.username layout="horizontal" %}
|
{% bootstrap_field form.username layout="horizontal" %}
|
||||||
{% bootstrap_field form.priority layout="horizontal" %}
|
{% bootstrap_field form.priority layout="horizontal" %}
|
||||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||||
{% bootstrap_field form.cluster layout="horizontal" %}
|
|
||||||
|
|
||||||
{% block auth %}
|
{% block auth %}
|
||||||
<h3>{% trans 'Auth' %}</h3>
|
<h3>{% trans 'Auth' %}</h3>
|
||||||
|
@ -52,8 +51,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-fields">
|
<div class="auth-fields">
|
||||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
|
||||||
{% bootstrap_field form.password layout="horizontal" %}
|
{% bootstrap_field form.password layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
<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 %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
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() {
|
function authFieldsDisplay() {
|
||||||
if ($(auto_generate_key).prop('checked')) {
|
if ($(auto_generate_key).prop('checked')) {
|
||||||
$('.auth-fields').addClass('hidden');
|
$('.auth-fields').addClass('hidden');
|
||||||
|
@ -89,9 +96,23 @@
|
||||||
$('.auth-fields').removeClass('hidden');
|
$('.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 () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2();
|
$('.select2').select2();
|
||||||
authFieldsDisplay();
|
authFieldsDisplay();
|
||||||
|
protocolChange();
|
||||||
$(auto_generate_key).change(function () {
|
$(auto_generate_key).change(function () {
|
||||||
authFieldsDisplay();
|
authFieldsDisplay();
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,14 +20,6 @@
|
||||||
<li class="active">
|
<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>
|
<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>
|
||||||
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
@ -59,7 +51,6 @@
|
||||||
<th>{% trans 'Hostname' %}</th>
|
<th>{% trans 'Hostname' %}</th>
|
||||||
<th>{% trans 'IP' %}</th>
|
<th>{% trans 'IP' %}</th>
|
||||||
<th>{% trans 'Port' %}</th>
|
<th>{% trans 'Port' %}</th>
|
||||||
<th>{% trans 'Type' %}</th>
|
|
||||||
<th>{% trans 'Reachable' %}</th>
|
<th>{% trans 'Reachable' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</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>';
|
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));
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
}},
|
}},
|
||||||
{targets: 5, createdCell: function (td, cellData) {
|
{targets: 4, createdCell: function (td, cellData) {
|
||||||
if (!cellData) {
|
if (!cellData) {
|
||||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,8 +108,9 @@ function initTable() {
|
||||||
}
|
}
|
||||||
}}],
|
}}],
|
||||||
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
|
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
|
||||||
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
columns: [
|
||||||
{data: "type" }, {data: "is_connective" }],
|
{data: function(){return ""}}, {data: "hostname" }, {data: "ip" },
|
||||||
|
{data: "port" }, {data: "is_connective" }],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initServerSideDataTable(options);
|
jumpserver.initServerSideDataTable(options);
|
||||||
|
@ -127,14 +119,6 @@ function initTable() {
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
initTable();
|
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 () {
|
.on('click', '.btn-test-connective', function () {
|
||||||
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
|
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
|
||||||
var error = function (data) {
|
var error = function (data) {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'Create admin user' %}</h5>
|
<h5>{{ action }}</h5>
|
||||||
<div class="ibox-tools">
|
<div class="ibox-tools">
|
||||||
<a class="collapse-link">
|
<a class="collapse-link">
|
||||||
<i class="fa fa-chevron-up"></i>
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<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 float-e-margins">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<span class="label"><b>{{ admin_user.name }}</b></span>
|
<span class="label"><b>{{ admin_user.name }}</b></span>
|
||||||
|
@ -77,11 +77,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||||
|
<div class="panel panel-primary">
|
||||||
<div class="panel panel-info">
|
|
||||||
<div class="panel-heading">
|
<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>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table group_edit" id="table-clusters">
|
<table class="table group_edit" id="table-clusters">
|
||||||
|
@ -89,25 +88,19 @@
|
||||||
<form>
|
<form>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<td colspan="2" class="no-borders">
|
||||||
<select data-placeholder="{% trans 'Select cluster' %}" id="cluster_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for cluster in cluster_remain %}
|
{% for node in nodes %}
|
||||||
<option value="{{ cluster.id }}" id="opt_{{ cluster.id }}" >{{ cluster.name }}</option>
|
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.value }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</form>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -123,29 +116,20 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
function bindToCluster(clusters) {
|
function replaceNodeAssetsAdminUser(nodes) {
|
||||||
var the_url = "{% url 'api-assets:admin-user-add-clusters' pk=admin_user.id %}";
|
var the_url = "{% url 'api-assets:replace-nodes-admin-user' pk=admin_user.id %}";
|
||||||
var body = {
|
var body = {
|
||||||
clusters: clusters
|
nodes: nodes
|
||||||
};
|
};
|
||||||
var success = function(data) {
|
var success = function(data) {
|
||||||
// remove all the selected groups from select > option and rendered ul element;
|
// remove all the selected groups from select > option and rendered ul element;
|
||||||
$('.select2-selection__rendered').empty();
|
$('.select2-selection__rendered').empty();
|
||||||
$('#cluster_selected').val('');
|
$('#nodes_selected').val('');
|
||||||
$.map(jumpserver.cluster_selected, function(cluster_name, index) {
|
$.map(jumpserver.nodes_selected, function(value, 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) {
|
|
||||||
$('#opt_' + index).remove();
|
$('#opt_' + index).remove();
|
||||||
});
|
});
|
||||||
// clear jumpserver.groups_selected
|
// clear jumpserver.groups_selected
|
||||||
jumpserver.cluster_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -154,15 +138,15 @@ function bindToCluster(clusters) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpserver.cluster_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2()
|
$('.select2').select2()
|
||||||
.on('select2:select', function(evt) {
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
jumpserver.cluster_selected[data.id] = data.text;
|
jumpserver.nodes_selected[data.id] = data.text;
|
||||||
}).on('select2:unselect', function(evt) {
|
}).on('select2:unselect', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
delete jumpserver.cluster_selected[data.id]
|
delete jumpserver.nodes_selected[data.id]
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('click', '.btn-delete-admin-user', function () {
|
.on('click', '.btn-delete-admin-user', function () {
|
||||||
|
@ -173,15 +157,15 @@ $(document).ready(function () {
|
||||||
var redirect_url = "{% url 'assets:admin-user-list' %}";
|
var redirect_url = "{% url 'assets:admin-user-list' %}";
|
||||||
objectDelete($this, name, the_url, redirect_url);
|
objectDelete($this, name, the_url, redirect_url);
|
||||||
})
|
})
|
||||||
.on('click', '#btn-add-cluster', function () {
|
.on('click', '#btn-change-admin-user', function () {
|
||||||
if (Object.keys(jumpserver.cluster_selected).length === 0) {
|
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var clusters = [];
|
var nodes = [];
|
||||||
$.map(jumpserver.cluster_selected, function(value, index) {
|
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||||
clusters.push(index);
|
nodes.push(index);
|
||||||
});
|
});
|
||||||
bindToCluster(clusters)
|
replaceNodeAssetsAdminUser(nodes);
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
{% block help_message %}
|
{% block help_message %}
|
||||||
<div class="alert alert-info help-message">
|
<div class="alert alert-info help-message">
|
||||||
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。可以设置主机级别管理用户,也设置集群级别管理用户,这样资产可以不用再单独设置
|
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -74,7 +74,8 @@ $(document).ready(function(){
|
||||||
if (val === 100) {
|
if (val === 100) {
|
||||||
innerHtml = "<span class='text-navy'>" + val + "% </span>";
|
innerHtml = "<span class='text-navy'>" + val + "% </span>";
|
||||||
} else {
|
} 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>');
|
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load asset_tags %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<form action="" method="post" class="form-horizontal">
|
<form action="" method="post" class="form-horizontal">
|
||||||
|
@ -15,25 +17,48 @@
|
||||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||||
{% bootstrap_field form.ip layout="horizontal" %}
|
{% bootstrap_field form.ip layout="horizontal" %}
|
||||||
{% bootstrap_field form.port 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.public_ip layout="horizontal" %}
|
||||||
{% bootstrap_field form.type layout="horizontal" %}
|
|
||||||
{% bootstrap_field form.env layout="horizontal" %}
|
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Auth' %}</h3>
|
<h3>{% trans 'Auth' %}</h3>
|
||||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Group' %}</h3>
|
<h3>{% trans 'Node' %}</h3>
|
||||||
{% bootstrap_field form.groups layout="horizontal" %}
|
{% 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>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Other' %}</h3>
|
<h3>{% trans 'Other' %}</h3>
|
||||||
{% bootstrap_field form.comment layout="horizontal" %}
|
{% bootstrap_field form.comment layout="horizontal" %}
|
||||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||||
|
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-4 col-sm-offset-2">
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
@ -45,11 +70,20 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
function format(item) {
|
||||||
$('.select2').select2({
|
var group = item.element.parentElement.label;
|
||||||
allowClear: true
|
return group + ':' + item.text;
|
||||||
});
|
}
|
||||||
})
|
|
||||||
</script>
|
$(document).ready(function () {
|
||||||
|
$('.select2').select2({
|
||||||
|
allowClear: true
|
||||||
|
});
|
||||||
|
$(".labels").select2({
|
||||||
|
allowClear: true,
|
||||||
|
templateSelection: format
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -71,27 +71,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Admin user' %}:</td>
|
<td>{% trans 'Admin user' %}:</td>
|
||||||
{% if asset.admin_user_avail %}
|
<td><b>{{ asset.admin_user }}</b></td>
|
||||||
<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>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Vendor' %}:</td>
|
<td>{% trans 'Vendor' %}:</td>
|
||||||
|
@ -121,22 +101,10 @@
|
||||||
<td>{% trans 'OS' %}:</td>
|
<td>{% trans 'OS' %}:</td>
|
||||||
<td><b>{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}</b></td>
|
<td><b>{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>{% trans 'Asset status' %}:</td>
|
|
||||||
<td><b>{{ asset.status }}</b></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Is active' %}:</td>
|
<td>{% trans 'Is active' %}:</td>
|
||||||
<td><b>{{ asset.is_active|yesno:"Yes,No" }}</b></td>
|
<td><b>{{ asset.is_active|yesno:"Yes,No" }}</b></td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td>{% trans 'Serial number' %}:</td>
|
<td>{% trans 'Serial number' %}:</td>
|
||||||
<td><b>{{ asset.sn|default:"" }}</b></td>
|
<td><b>{{ asset.sn|default:"" }}</b></td>
|
||||||
|
@ -210,7 +178,7 @@
|
||||||
|
|
||||||
<div class="panel panel-info">
|
<div class="panel panel-info">
|
||||||
<div class="panel-heading">
|
<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>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table group_edit" id="add-asset2group">
|
<table class="table group_edit" id="add-asset2group">
|
||||||
|
@ -218,25 +186,25 @@
|
||||||
<form>
|
<form>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<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">
|
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for asset_group in asset_groups_remain %}
|
{% for node in nodes_remain %}
|
||||||
<option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}" >{{ asset_group.name }}</option>
|
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% for asset_group in asset_groups %}
|
{% for node in asset.nodes.all %}
|
||||||
<tr>
|
<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>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -244,6 +212,19 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -254,28 +235,28 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
jumpserver.groups_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
function updateAssetGroups(groups) {
|
function updateAssetNodes(nodes) {
|
||||||
var the_url = "{% url 'api-assets:asset-detail' pk=asset.id %}";
|
var the_url = "{% url 'api-assets:asset-detail' pk=asset.id %}";
|
||||||
var body = {
|
var body = {
|
||||||
groups: Object.assign([], groups)
|
nodes: Object.assign([], nodes)
|
||||||
};
|
};
|
||||||
var success = function(data) {
|
var success = function(data) {
|
||||||
// remove all the selected groups from select > option and rendered ul element;
|
// remove all the selected groups from select > option and rendered ul element;
|
||||||
$('.select2-selection__rendered').empty();
|
$('.select2-selection__rendered').empty();
|
||||||
$('#groups_selected').val('');
|
$('#groups_selected').val('');
|
||||||
$.map(jumpserver.groups_selected, function(group_name, index) {
|
$.map(jumpserver.nodes_selected, function(group_name, index) {
|
||||||
$('#opt_' + index).remove();
|
$('#opt_' + index).remove();
|
||||||
// change tr html of user groups.
|
// change tr html of user groups.
|
||||||
$('#add-asset2group tbody').append(
|
$('#add-asset2group tbody').append(
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></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-group" type="button"><i class="fa fa-minus"></i></button></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>'
|
'</tr>'
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
// clear jumpserver.groups_selected
|
// clear jumpserver.groups_selected
|
||||||
jumpserver.groups_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -304,10 +285,10 @@ function refreshAssetHardware() {
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2.groups').select2().on('select2:select', function(evt) {
|
$('.select2.groups').select2().on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
jumpserver.groups_selected[data.id] = data.text;
|
jumpserver.nodes_selected[data.id] = data.text;
|
||||||
}).on('select2:unselect', function(evt) {
|
}).on('select2:unselect', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
delete jumpserver.groups_selected[data.id]
|
delete jumpserver.nodes_selected[data.id]
|
||||||
});
|
});
|
||||||
}).on('click', '#is_active', function () {
|
}).on('click', '#is_active', function () {
|
||||||
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
|
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
|
||||||
|
@ -322,37 +303,37 @@ $(document).ready(function () {
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
success_message: success
|
success_message: success
|
||||||
});
|
});
|
||||||
if (status == "False") {
|
if (status === "False") {
|
||||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
|
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
|
||||||
}else{
|
}else{
|
||||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
|
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
|
||||||
}
|
}
|
||||||
}).on('click', '#btn-add-user-group', function () {
|
}).on('click', '#btn-update-nodes', function () {
|
||||||
if (Object.keys(jumpserver.groups_selected).length === 0) {
|
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var groups = $('.bdg_group').map(function() {
|
var nodes = $('.bdg_node').map(function() {
|
||||||
return $(this).data('gid');
|
return $(this).data('gid');
|
||||||
}).get();
|
}).get();
|
||||||
$.map(jumpserver.groups_selected, function(value, index) {
|
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||||
groups.push(index);
|
nodes.push(index);
|
||||||
$('#opt_' + index).remove();
|
$('#opt_' + index).remove();
|
||||||
});
|
});
|
||||||
updateAssetGroups(groups)
|
updateAssetNodes(nodes)
|
||||||
}).on('click', '.btn-leave-group', function() {
|
}).on('click', '.btn-leave-node', function() {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $tr = $this.closest('tr');
|
var $tr = $this.closest('tr');
|
||||||
var $badge = $tr.find('.bdg_group');
|
var $badge = $tr.find('.bdg_node');
|
||||||
var gid = $badge.data('gid');
|
var gid = $badge.data('gid');
|
||||||
var group_name = $badge.html() || $badge.text();
|
var group_name = $badge.html() || $badge.text();
|
||||||
$('#groups_selected').append(
|
$('#groups_selected').append(
|
||||||
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
|
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
|
||||||
);
|
);
|
||||||
$tr.remove();
|
$tr.remove();
|
||||||
var groups = $('.bdg_group').map(function () {
|
var groups = $('.bdg_node').map(function () {
|
||||||
return $(this).data('gid');
|
return $(this).data('gid');
|
||||||
}).get();
|
}).get();
|
||||||
updateAssetGroups(groups)
|
updateAssetNodes(groups)
|
||||||
}).on('click', '.btn-delete-asset', function () {
|
}).on('click', '.btn-delete-asset', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = "{{ asset.hostname }}";
|
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,68 +1,139 @@
|
||||||
{% extends '_base_list.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block help_message %}
|
||||||
|
<div class="alert alert-info help-message">
|
||||||
|
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block custom_head_css_js %}
|
{% block custom_head_css_js %}
|
||||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
<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 %}
|
{% endblock %}
|
||||||
{% block content_left_head %}{% endblock %}
|
|
||||||
|
|
||||||
{% block table_search %}
|
{% block content %}
|
||||||
<div class="html5buttons">
|
<div class="wrapper wrapper-content">
|
||||||
<div class="dt-buttons btn-group">
|
<div class="row">
|
||||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
<div class="col-lg-3" id="split-left">
|
||||||
<span>{% trans "Import" %}</span>
|
<div class="ibox float-e-margins">
|
||||||
</a>
|
<div class="ibox-content mailbox-content" style="padding-top: 0">
|
||||||
<a class="btn btn-default btn_export" tabindex="0">
|
<div class="file-manager ">
|
||||||
<span>{% trans "Export" %}</span>
|
<div id="assetTree" class="ztree">
|
||||||
</a>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block table_container %}
|
<div class="clearfix"></div>
|
||||||
<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>
|
</div>
|
||||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
</div>
|
||||||
<thead>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
<div class="tree-toggle">
|
||||||
<th class="text-center">{% trans 'IP' %}</th>
|
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
|
||||||
<th class="text-center">{% trans 'Port' %}</th>
|
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||||
<th class="text-center">{% trans 'Cluster' %}</th>
|
</div>
|
||||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
</div>
|
||||||
<th class="text-center">{% trans 'Active' %}</th>
|
<div class="mail-box-header">
|
||||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||||
<th class="text-center">{% trans 'Action' %}</th>
|
<div class="html5buttons">
|
||||||
</tr>
|
<div class="dt-buttons btn-group">
|
||||||
</thead>
|
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||||
<tbody>
|
<span>{% trans "Import" %}</span>
|
||||||
</tbody>
|
</a>
|
||||||
</table>
|
<a class="btn btn-default btn_export" tabindex="0">
|
||||||
<div id="actions" class="hide">
|
<span>{% trans "Export" %}</span>
|
||||||
<div class="input-group">
|
</a>
|
||||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
</div>
|
||||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
</div>
|
||||||
<option value="update">{% trans 'Update selected' %}</option>
|
<div class="btn-group" style="float: right">
|
||||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||||
<option value="active">{% trans 'Active selected' %}</option>
|
<ul class="dropdown-menu labels">
|
||||||
</select>
|
{% for label in labels %}
|
||||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
|
||||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
{% endfor %}
|
||||||
{% trans 'Submit' %}
|
</ul>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
|
||||||
</div>
|
<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="remove">{% trans 'Remove from this node' %}</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>
|
||||||
|
</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_import_modal.html' %}
|
||||||
{#{% include 'assets/_asset_bulk_update_modal.html' %}#}
|
{% include 'assets/_asset_list_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
<script>
|
||||||
<script type="text/javascript">
|
var zTree, rMenu, asset_table, show = 0;
|
||||||
|
|
||||||
function initTable() {
|
function initTable() {
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#asset_list_table'),
|
ele: $('#asset_list_table'),
|
||||||
|
@ -72,21 +143,18 @@ function initTable() {
|
||||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
}},
|
}},
|
||||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||||
$(td).html(rowData.cluster_name)
|
|
||||||
}},
|
|
||||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
|
||||||
$(td).html(rowData.hardware_info)
|
$(td).html(rowData.hardware_info)
|
||||||
}},
|
}},
|
||||||
{targets: 6, createdCell: function (td, cellData) {
|
{targets: 4, createdCell: function (td, cellData) {
|
||||||
if (!cellData) {
|
if (!cellData) {
|
||||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{targets: 7, createdCell: function (td, cellData) {
|
{targets: 5, createdCell: function (td, cellData) {
|
||||||
if (cellData == 'Unknown'){
|
if (cellData === 'Unknown'){
|
||||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||||
} else if (!cellData) {
|
} else if (!cellData) {
|
||||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
$(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>')
|
$(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 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);
|
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)
|
$(td).html(update_btn + del_btn)
|
||||||
|
@ -102,19 +170,263 @@ function initTable() {
|
||||||
],
|
],
|
||||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||||
{data: "cluster"},
|
|
||||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
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(){
|
$(document).ready(function(){
|
||||||
initTable();
|
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 () {
|
.on('click', '.btn_export', function () {
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
var $data_table = $('#asset_list_table').DataTable();
|
||||||
var rows = $data_table.rows('.selected').data();
|
var rows = $data_table.rows('.selected').data();
|
||||||
|
@ -154,7 +466,16 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
$form.ajaxSubmit({success: success});
|
$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 () {
|
.on('click', '.btn_asset_delete', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $data_table = $("#asset_list_table").DataTable();
|
var $data_table = $("#asset_list_table").DataTable();
|
||||||
|
@ -185,7 +506,7 @@ $(document).ready(function(){
|
||||||
data.push(obj);
|
data.push(obj);
|
||||||
});
|
});
|
||||||
function success() {
|
function success() {
|
||||||
location.reload()
|
asset_table.ajax.reload()
|
||||||
}
|
}
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -201,7 +522,7 @@ $(document).ready(function(){
|
||||||
data.push(obj);
|
data.push(obj);
|
||||||
});
|
});
|
||||||
function success() {
|
function success() {
|
||||||
location.reload();
|
asset_table.ajax.reload()
|
||||||
}
|
}
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -245,6 +566,31 @@ $(document).ready(function(){
|
||||||
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
|
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
|
||||||
location.href = url
|
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) {
|
switch(action) {
|
||||||
case 'deactive':
|
case 'deactive':
|
||||||
doDeactive();
|
doDeactive();
|
||||||
|
@ -258,10 +604,13 @@ $(document).ready(function(){
|
||||||
case 'active':
|
case 'active':
|
||||||
doActive();
|
doActive();
|
||||||
break;
|
break;
|
||||||
|
case 'remove':
|
||||||
|
doRemove();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
|
||||||
|
{% 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 static %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load asset_tags %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
{% block custom_head_css_js_create %}
|
{% block custom_head_css_js_create %}
|
||||||
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
||||||
|
@ -20,32 +22,44 @@
|
||||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||||
{% bootstrap_field form.ip layout="horizontal" %}
|
{% bootstrap_field form.ip layout="horizontal" %}
|
||||||
{% bootstrap_field form.port 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.public_ip layout="horizontal" %}
|
||||||
{% bootstrap_field form.type layout="horizontal" %}
|
|
||||||
{% bootstrap_field form.env layout="horizontal" %}
|
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Auth' %}</h3>
|
<h3>{% trans 'Auth' %}</h3>
|
||||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Group' %}</h3>
|
<h3>{% trans 'Node' %}</h3>
|
||||||
{% bootstrap_field form.groups layout="horizontal" %}
|
{% 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>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Configuration' %}</h3>
|
<h3>{% trans 'Configuration' %}</h3>
|
||||||
{% bootstrap_field form.number layout="horizontal" %}
|
{% 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>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Other' %}</h3>
|
<h3>{% trans 'Other' %}</h3>
|
||||||
{% bootstrap_field form.status layout="horizontal" %}
|
|
||||||
{% bootstrap_field form.comment layout="horizontal" %}
|
{% bootstrap_field form.comment layout="horizontal" %}
|
||||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||||
|
|
||||||
|
@ -62,14 +76,18 @@
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
function format(item) {
|
||||||
$('.select2').select2({
|
var group = item.element.parentElement.label;
|
||||||
allowClear: true
|
return group + ':' + item.text;
|
||||||
});
|
}
|
||||||
$("#tags").select2({
|
$(document).ready(function () {
|
||||||
tags: true,
|
$('.select2').select2({
|
||||||
maximumSelectionLength: 8 //最多能够选择的个数
|
allowClear: true
|
||||||
});
|
});
|
||||||
})
|
$(".labels").select2({
|
||||||
|
allowClear: true,
|
||||||
|
templateSelection: format
|
||||||
|
});
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -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">
|
<form id="groupForm" method="post" class="form-horizontal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_field form.name layout="horizontal" %}
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.value layout="horizontal" %}
|
||||||
{% bootstrap_field form.assets layout="horizontal" %}
|
{% bootstrap_field form.assets layout="horizontal" %}
|
||||||
{% bootstrap_field form.comment layout="horizontal" %}
|
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<div class="form-group">
|
<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' %}
|
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
<li class="active">
|
<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>
|
<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>
|
||||||
<li>
|
{# <li>#}
|
||||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
{# <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' %}
|
{# <i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}#}
|
||||||
</a>
|
{# </a>#}
|
||||||
</li>
|
{# </li>#}
|
||||||
<li class="pull-right">
|
<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>
|
<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>
|
</li>
|
||||||
|
@ -130,6 +130,23 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>#}
|
{# <tr>#}
|
||||||
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
||||||
{# <td>#}
|
{# <td>#}
|
||||||
|
@ -144,33 +161,33 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-info">
|
<div class="panel panel-info">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<i class="fa fa-info-circle"></i> {% trans 'Clusters' %}
|
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table cluster_edit" id="add-asset2group">
|
<table class="table node_edit" id="add-asset2group">
|
||||||
<tbody>
|
<tbody>
|
||||||
<form>
|
<form>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<td colspan="2" class="no-borders">
|
||||||
<select data-placeholder="{% trans 'Add to cluster' %}" id="cluster_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for cluster in cluster_remain %}
|
{% for node in nodes_remain %}
|
||||||
<option value="{{ cluster.id }}" id="opt_{{ cluster.id }}" >{{ cluster.name }}</option>
|
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% for cluster in system_user.cluster.all %}
|
{% for node in system_user.nodes.all %}
|
||||||
<tr>
|
<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>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -187,27 +204,27 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
function updateSystemUserCluster(clusters) {
|
function updateSystemUserCluster(nodes) {
|
||||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||||
var body = {
|
var body = {
|
||||||
cluster: Object.assign([], clusters)
|
nodes: Object.assign([], nodes)
|
||||||
};
|
};
|
||||||
var success = function(data) {
|
var success = function(data) {
|
||||||
// remove all the selected groups from select > option and rendered ul element;
|
// remove all the selected groups from select > option and rendered ul element;
|
||||||
$('.select2-selection__rendered').empty();
|
$('.select2-selection__rendered').empty();
|
||||||
$('#cluster_selected').val('');
|
$('#node_selected').val('');
|
||||||
$.map(jumpserver.cluster_selected, function(cluster_name, index) {
|
$.map(jumpserver.nodes_selected, function(node_name, index) {
|
||||||
$('#opt_' + index).remove();
|
$('#opt_' + index).remove();
|
||||||
// change tr html of user groups.
|
// change tr html of user groups.
|
||||||
$('.cluster_edit tbody').append(
|
$('.node_edit tbody').append(
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td><b class="bdg_cluster" data-gid="' + index + '">' + cluster_name + '</b></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-cluster" type="button"><i class="fa fa-minus"></i></button></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>'
|
'</tr>'
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
// clear jumpserver.groups_selected
|
// clear jumpserver.groups_selected
|
||||||
jumpserver.cluster_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -215,16 +232,16 @@ function updateSystemUserCluster(clusters) {
|
||||||
success: success
|
success: success
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
jumpserver.cluster_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2()
|
$('.select2').select2()
|
||||||
.on('select2:select', function(evt) {
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
jumpserver.cluster_selected[data.id] = data.text;
|
jumpserver.nodes_selected[data.id] = data.text;
|
||||||
})
|
})
|
||||||
.on('select2:unselect', function(evt) {
|
.on('select2:unselect', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
delete jumpserver.cluster_selected[data.id];
|
delete jumpserver.nodes_selected[data.id];
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('click', '#btn-auto-push', function () {
|
.on('click', '#btn-auto-push', function () {
|
||||||
|
@ -238,32 +255,32 @@ $(document).ready(function () {
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('click', '#btn-add-to-cluster', function() {
|
.on('click', '#btn-add-to-node', function() {
|
||||||
if (Object.keys(jumpserver.cluster_selected).length === 0) {
|
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var clusters = $('.bdg_cluster').map(function() {
|
var nodes = $('.bdg_node').map(function() {
|
||||||
return $(this).data('gid');
|
return $(this).data('gid');
|
||||||
}).get();
|
}).get();
|
||||||
$.map(jumpserver.cluster_selected, function(value, index) {
|
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||||
clusters.push(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 $this = $(this);
|
||||||
var $tr = $this.closest('tr');
|
var $tr = $this.closest('tr');
|
||||||
var $badge = $tr.find('.bdg_cluster');
|
var $badge = $tr.find('.bdg_node');
|
||||||
var gid = $badge.data('gid');
|
var gid = $badge.data('gid');
|
||||||
var cluster_name = $badge.html() || $badge.text();
|
var node_name = $badge.html() || $badge.text();
|
||||||
$('#groups_selected').append(
|
$('#groups_selected').append(
|
||||||
'<option value="' + gid + '" id="opt_' + gid + '">' + cluster_name + '</option>'
|
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
|
||||||
);
|
);
|
||||||
$tr.remove();
|
$tr.remove();
|
||||||
var clusters = $('.bdg_cluster').map(function () {
|
var nodes = $('.bdg_node').map(function () {
|
||||||
return $(this).data('gid');
|
return $(this).data('gid');
|
||||||
}).get();
|
}).get();
|
||||||
updateSystemUserCluster(clusters);
|
updateSystemUserCluster(nodes);
|
||||||
}).on('click', '.btn-del', function () {
|
}).on('click', '.btn-del', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = "{{ system_user.name}}";
|
var name = "{{ system_user.name}}";
|
||||||
|
@ -272,5 +289,29 @@ $(document).ready(function () {
|
||||||
var redirect_url = "{% url 'assets:system-user-list' %}";
|
var redirect_url = "{% url 'assets:system-user-list' %}";
|
||||||
objectDelete($this, name, the_url, redirect_url);
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% block help_message %}
|
{% block help_message %}
|
||||||
<div class="alert alert-info help-message">
|
<div class="alert alert-info help-message">
|
||||||
系统用户是 用户登录资产(服务器)时使用的用户,如 web, sa, dba等具有特殊功能的用户。系统用户创建时,如果选择了自动推送
|
系统用户是 用户登录资产(服务器)时使用的用户,如 web, sa, dba等具有特殊功能的用户。系统用户创建时,如果选择了自动推送
|
||||||
Jumpserver会使用ansible自动推送到系统用户所在集群的资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。
|
Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -75,7 +75,8 @@ function initTable() {
|
||||||
if (val === 100) {
|
if (val === 100) {
|
||||||
innerHtml = "<span class='text-navy'>" + val + "% </span>";
|
innerHtml = "<span class='text-navy'>" + val + "% </span>";
|
||||||
} else {
|
} 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>');
|
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
{% block auth %}
|
{% block auth %}
|
||||||
<h3>{% trans 'Auth' %}</h3>
|
<h3>{% trans 'Auth' %}</h3>
|
||||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
|
||||||
{% bootstrap_field form.password layout="horizontal" %}
|
{% bootstrap_field form.password layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||||
<th class="text-center">{% trans 'IP' %}</th>
|
<th class="text-center">{% trans 'IP' %}</th>
|
||||||
<th class="text-center">{% trans 'Port' %}</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 'Hardware' %}</th>
|
||||||
<th class="text-center">{% trans 'Active' %}</th>
|
<th class="text-center">{% trans 'Active' %}</th>
|
||||||
<th class="text-center">{% trans 'Connective' %}</th>
|
<th class="text-center">{% trans 'Connective' %}</th>
|
||||||
|
@ -44,14 +42,14 @@ function initTable() {
|
||||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
}},
|
}},
|
||||||
{targets: 7, createdCell: function (td, cellData) {
|
{targets: 5, createdCell: function (td, cellData) {
|
||||||
if (!cellData) {
|
if (!cellData) {
|
||||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
$(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'){
|
if (cellData == 'Unknown'){
|
||||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||||
} else if (!cellData) {
|
} else if (!cellData) {
|
||||||
|
@ -68,8 +66,7 @@ function initTable() {
|
||||||
ajax_url: '{% url "api-assets:user-asset-list" %}',
|
ajax_url: '{% url "api-assets:user-asset-list" %}',
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"},
|
{data: "hardware_info"}, {data: "is_active" }, {data: "is_connective"}
|
||||||
{data: "is_active" }, {data: "is_connective"}
|
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
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 import template
|
||||||
from django.utils import timezone
|
|
||||||
from django.conf import settings
|
|
||||||
register = template.Library()
|
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 = 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/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/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-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 = [
|
urlpatterns = [
|
||||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||||
|
@ -24,23 +26,27 @@ urlpatterns = [
|
||||||
url(r'^v1/assets/user-assets/$',
|
url(r'^v1/assets/user-assets/$',
|
||||||
api.UserAssetListView.as_view(), name='user-asset-list'),
|
api.UserAssetListView.as_view(), name='user-asset-list'),
|
||||||
# update the asset group, which add or delete the asset to the group
|
# 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/$',
|
#url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||||
api.GroupUpdateAssetsApi.as_view(), name='group-update-assets'),
|
# api.GroupUpdateAssetsApi.as_view(), name='group-update-assets'),
|
||||||
url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
|
#url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
|
||||||
api.GroupAddAssetsApi.as_view(), name='group-add-assets'),
|
# api.GroupAddAssetsApi.as_view(), name='group-add-assets'),
|
||||||
# update the Cluster, and add or delete the assets to the Cluster
|
# update the Cluster, and add or delete the assets to the Cluster
|
||||||
url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
#url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||||
api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'),
|
# api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'),
|
||||||
url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/connective/$',
|
#url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/connective/$',
|
||||||
api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'),
|
# api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'),
|
||||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/clusters/$',
|
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||||
api.AdminUserAddClustersApi.as_view(), name='admin-user-add-clusters'),
|
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$',
|
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$',
|
||||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-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
|
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})/$', 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})/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/(?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'),
|
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||||
|
|
||||||
# User asset view
|
# User asset view
|
||||||
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
|
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
|
# Resource admin user url
|
||||||
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
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'),
|
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})/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})/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/$', 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 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
#
|
#
|
||||||
from collections import defaultdict
|
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 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):
|
def get_assets_by_id_list(id_list):
|
||||||
|
@ -18,12 +23,26 @@ def get_system_user_by_name(name):
|
||||||
return system_user
|
return system_user
|
||||||
|
|
||||||
|
|
||||||
def check_assets_have_system_user(assets, system_users):
|
class LabelFilter:
|
||||||
errors = defaultdict(list)
|
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
|
# coding:utf-8
|
||||||
from .asset import *
|
from .asset import *
|
||||||
from .group import *
|
|
||||||
from .cluster import *
|
|
||||||
from .system_user import *
|
from .system_user import *
|
||||||
from .admin_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 common.const import create_success_msg, update_success_msg
|
||||||
from .. import forms
|
from .. import forms
|
||||||
from ..models import AdminUser, Cluster
|
from ..models import AdminUser, Node
|
||||||
from ..hands import AdminUserRequiredMixin
|
from ..hands import AdminUserRequiredMixin
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -74,11 +74,10 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
object = None
|
object = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
cluster_remain = Cluster.objects.exclude(admin_user=self.object)
|
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Admin user detail'),
|
'action': _('Admin user detail'),
|
||||||
'cluster_remain': cluster_remain,
|
'nodes': Node.objects.all()
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
@ -95,11 +94,8 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = []
|
self.queryset = self.object.asset_set.all()
|
||||||
for cluster in self.object.cluster_set.all():
|
return self.queryset
|
||||||
queryset.extend([asset for asset in cluster.assets.all() if not asset.admin_user])
|
|
||||||
self.queryset = queryset
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
|
|
|
@ -27,15 +27,14 @@ from common.mixins import JSONResponseMixin
|
||||||
from common.utils import get_object_or_none, get_logger, is_uuid
|
from common.utils import get_object_or_none, get_logger, is_uuid
|
||||||
from common.const import create_success_msg, update_success_msg
|
from common.const import create_success_msg, update_success_msg
|
||||||
from .. import forms
|
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
|
from ..hands import AdminUserRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetListView', 'AssetCreateView', 'AssetUpdateView',
|
'AssetListView', 'AssetCreateView', 'AssetUpdateView',
|
||||||
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
||||||
'AssetModalListView', 'AssetDeleteView', 'AssetExportView',
|
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView',
|
||||||
'BulkImportAssetView',
|
|
||||||
]
|
]
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
@ -44,10 +43,11 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
|
||||||
template_name = 'assets/asset_list.html'
|
template_name = 'assets/asset_list.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
Node.root()
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Asset list'),
|
'action': _('Asset list'),
|
||||||
'system_users': SystemUser.objects.all(),
|
'labels': Label.objects.all().order_by('name'),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
@ -58,8 +58,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'action': _('My assets'),
|
||||||
'action': _('Asset list'),
|
|
||||||
'system_users': SystemUser.objects.all(),
|
'system_users': SystemUser.objects.all(),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
|
@ -72,12 +71,23 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||||
template_name = 'assets/asset_create.html'
|
template_name = 'assets/asset_create.html'
|
||||||
success_url = reverse_lazy('assets:asset-list')
|
success_url = reverse_lazy('assets:asset-list')
|
||||||
|
|
||||||
def form_valid(self, form):
|
# def form_valid(self, form):
|
||||||
asset = form.save()
|
# print("form valid")
|
||||||
asset.created_by = self.request.user.username or 'Admin'
|
# asset = form.save()
|
||||||
asset.date_created = timezone.now()
|
# asset.created_by = self.request.user.username or 'Admin'
|
||||||
asset.save()
|
# asset.date_created = timezone.now()
|
||||||
return super().form_valid(form)
|
# 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):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
|
@ -91,22 +101,22 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||||
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
||||||
|
|
||||||
|
|
||||||
class AssetModalListView(AdminUserRequiredMixin, ListView):
|
# class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||||
paginate_by = settings.DISPLAY_PER_PAGE
|
# paginate_by = settings.DISPLAY_PER_PAGE
|
||||||
model = Asset
|
# model = Asset
|
||||||
context_object_name = 'asset_modal_list'
|
# context_object_name = 'asset_modal_list'
|
||||||
template_name = 'assets/asset_modal_list.html'
|
# template_name = 'assets/_asset_list_modal.html'
|
||||||
|
#
|
||||||
def get_context_data(self, **kwargs):
|
# def get_context_data(self, **kwargs):
|
||||||
assets = Asset.objects.all()
|
# assets = Asset.objects.all()
|
||||||
assets_id = self.request.GET.get('assets_id', '')
|
# assets_id = self.request.GET.get('assets_id', '')
|
||||||
assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
|
# assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
|
||||||
context = {
|
# context = {
|
||||||
'all_assets': assets_id_list,
|
# 'all_assets': assets_id_list,
|
||||||
'assets': assets
|
# 'assets': assets
|
||||||
}
|
# }
|
||||||
kwargs.update(context)
|
# kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
# return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||||
|
@ -180,14 +190,11 @@ class AssetDetailView(DetailView):
|
||||||
template_name = 'assets/asset_detail.html'
|
template_name = 'assets/asset_detail.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
asset_groups = self.object.groups.all()
|
nodes_remain = Node.objects.exclude(assets=self.object)
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Asset detail'),
|
'action': _('Asset detail'),
|
||||||
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
|
'nodes_remain': nodes_remain,
|
||||||
if asset_group not in asset_groups],
|
|
||||||
'asset_groups': asset_groups,
|
|
||||||
'system_users_all': SystemUser.objects.all(),
|
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
@ -206,22 +213,19 @@ class AssetExportView(View):
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
filename = 'assets-{}.csv'.format(
|
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 = HttpResponse(content_type='text/csv')
|
||||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||||
response.write(codecs.BOM_UTF8)
|
response.write(codecs.BOM_UTF8)
|
||||||
assets = Asset.objects.filter(id__in=assets_id)
|
assets = Asset.objects.filter(id__in=assets_id)
|
||||||
writer = csv.writer(response, dialect='excel',
|
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
||||||
quoting=csv.QUOTE_MINIMAL)
|
|
||||||
|
|
||||||
header = [field.verbose_name for field in fields]
|
header = [field.verbose_name for field in fields]
|
||||||
header.append(_('Asset groups'))
|
|
||||||
writer.writerow(header)
|
writer.writerow(header)
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
groups = ','.join([group.name for group in asset.groups.all()])
|
|
||||||
data = [getattr(asset, field.name) for field in fields]
|
data = [getattr(asset, field.name) for field in fields]
|
||||||
data.append(groups)
|
|
||||||
writer.writerow(data)
|
writer.writerow(data)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -243,6 +247,7 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
f = form.cleaned_data['file']
|
f = form.cleaned_data['file']
|
||||||
det_result = chardet.detect(f.read())
|
det_result = chardet.detect(f.read())
|
||||||
f.seek(0) # reset file seek index
|
f.seek(0) # reset file seek index
|
||||||
|
|
||||||
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
||||||
csv_file = StringIO(file_data)
|
csv_file = StringIO(file_data)
|
||||||
reader = csv.reader(csv_file)
|
reader = csv.reader(csv_file)
|
||||||
|
@ -255,7 +260,6 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
]
|
]
|
||||||
header_ = csv_data[0]
|
header_ = csv_data[0]
|
||||||
mapping_reverse = {field.verbose_name: field.name for field in fields}
|
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_]
|
attr = [mapping_reverse.get(n, None) for n in header_]
|
||||||
if None in attr:
|
if None in attr:
|
||||||
data = {'valid': False,
|
data = {'valid': False,
|
||||||
|
@ -272,20 +276,15 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
asset_dict = dict(zip(attr, row))
|
asset_dict = dict(zip(attr, row))
|
||||||
id_ = asset_dict.pop('id', 0)
|
id_ = asset_dict.pop('id', 0)
|
||||||
for k, v in asset_dict.items():
|
for k, v in asset_dict.items():
|
||||||
if k == 'cluster':
|
if k == 'is_active':
|
||||||
v = get_object_or_none(Cluster, name=v)
|
v = True if v in ['TRUE', 1, 'true'] else False
|
||||||
elif k == 'is_active':
|
|
||||||
v = bool(v)
|
|
||||||
elif k == 'admin_user':
|
elif k == 'admin_user':
|
||||||
v = get_object_or_none(AdminUser, name=v)
|
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:
|
try:
|
||||||
v = int(v)
|
v = int(v)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
v = 0
|
v = 0
|
||||||
elif k == 'groups':
|
|
||||||
groups_name = v.split(',')
|
|
||||||
v = AssetGroup.objects.filter(name__in=groups_name)
|
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
asset_dict[k] = v
|
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
|
asset = get_object_or_none(Asset, id=id_) if is_uuid(id_) else None
|
||||||
if not asset:
|
if not asset:
|
||||||
try:
|
try:
|
||||||
groups = asset_dict.pop('groups')
|
|
||||||
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
|
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
|
||||||
raise Exception(_('already exists'))
|
raise Exception(_('already exists'))
|
||||||
asset = Asset.objects.create(**asset_dict)
|
asset = Asset.objects.create(**asset_dict)
|
||||||
asset.groups.set(groups)
|
|
||||||
created.append(asset_dict['hostname'])
|
created.append(asset_dict['hostname'])
|
||||||
assets.append(asset)
|
assets.append(asset)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
||||||
else:
|
else:
|
||||||
for k, v in asset_dict.items():
|
for k, v in asset_dict.items():
|
||||||
if k == 'groups':
|
|
||||||
asset.groups.set(v)
|
|
||||||
continue
|
|
||||||
if v:
|
if v:
|
||||||
setattr(asset, k, v)
|
setattr(asset, k, v)
|
||||||
try:
|
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 django.views.generic.detail import DetailView
|
||||||
|
|
||||||
from common.const import create_success_msg, update_success_msg
|
from common.const import create_success_msg, update_success_msg
|
||||||
from ..forms import SystemUserForm, SystemUserUpdateForm
|
from ..forms import SystemUserForm
|
||||||
from ..models import SystemUser, Cluster
|
from ..models import SystemUser, Node
|
||||||
from ..hands import AdminUserRequiredMixin
|
from ..hands import AdminUserRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
|
||||||
|
|
||||||
class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
form_class = SystemUserUpdateForm
|
form_class = SystemUserForm
|
||||||
template_name = 'assets/system_user_update.html'
|
template_name = 'assets/system_user_update.html'
|
||||||
success_url = reverse_lazy('assets:system-user-list')
|
success_url = reverse_lazy('assets:system-user-list')
|
||||||
success_message = update_success_msg
|
success_message = update_success_msg
|
||||||
|
@ -70,11 +70,10 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
cluster_remain = Cluster.objects.exclude(systemuser=self.object)
|
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('System user detail'),
|
'action': _('System user detail'),
|
||||||
'cluster_remain': cluster_remain,
|
'nodes_remain': Node.objects.exclude(systemuser=self.object)
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
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.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from .permissions import IsSuperUser
|
from .permissions import IsSuperUser, IsAppUser
|
||||||
from .serializers import MailTestSerializer, LDAPTestSerializer
|
from .serializers import MailTestSerializer, LDAPTestSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,8 +63,6 @@ class LDAPTestingAPI(APIView):
|
||||||
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
|
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
|
||||||
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
|
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
|
||||||
|
|
||||||
print(serializer.validated_data)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attr_map = json.loads(attr_map)
|
attr_map = json.loads(attr_map)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
|
@ -77,9 +75,6 @@ class LDAPTestingAPI(APIView):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response({"error": str(e)}, status=401)
|
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": "*"}),
|
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
|
||||||
attributes=list(attr_map.values()))
|
attributes=list(attr_map.values()))
|
||||||
if not ok:
|
if not ok:
|
||||||
|
@ -93,7 +88,7 @@ class LDAPTestingAPI(APIView):
|
||||||
user[attr] = getattr(entry, mapping)
|
user[attr] = getattr(entry, mapping)
|
||||||
users.append(user)
|
users.append(user)
|
||||||
if len(users) > 0:
|
if len(users) > 0:
|
||||||
return Response({"msg": "Match {} s users".format(len(users))})
|
return Response({"msg": _("Match {} s users").format(len(users))})
|
||||||
else:
|
else:
|
||||||
return Response({"error": "Have user but attr mapping error"}, status=401)
|
return Response({"error": "Have user but attr mapping error"}, status=401)
|
||||||
else:
|
else:
|
||||||
|
@ -102,9 +97,11 @@ class LDAPTestingAPI(APIView):
|
||||||
|
|
||||||
class DjangoSettingsAPI(APIView):
|
class DjangoSettingsAPI(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
if not settings.DEBUG:
|
||||||
|
return Response('Only debug mode support')
|
||||||
|
|
||||||
configs = {}
|
configs = {}
|
||||||
for i in dir(settings):
|
for i in dir(settings):
|
||||||
if i.isupper():
|
if i.isupper():
|
||||||
configs[i] = str(getattr(settings, i))
|
configs[i] = str(getattr(settings, i))
|
||||||
return Response(configs)
|
return Response(configs)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import json
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
class DictField(forms.Field):
|
class DictField(forms.Field):
|
||||||
|
@ -18,16 +20,16 @@ class DictField(forms.Field):
|
||||||
# we don't need to handle that explicitly.
|
# we don't need to handle that explicitly.
|
||||||
if isinstance(value, six.string_types):
|
if isinstance(value, six.string_types):
|
||||||
try:
|
try:
|
||||||
print(value)
|
|
||||||
value = json.loads(value)
|
value = json.loads(value)
|
||||||
return value
|
return value
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
return ValidationError(_("Not a valid json"))
|
||||||
value = {}
|
else:
|
||||||
return value
|
return ValidationError(_("Not a string type"))
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
print(value)
|
if isinstance(value, ValidationError):
|
||||||
|
raise value
|
||||||
if not value and self.required:
|
if not value and self.required:
|
||||||
raise ValidationError(self.error_messages['required'], code='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
|
# 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
|
# so we should run it through to_python first to get a boolean value
|
||||||
return self.to_python(initial) != self.to_python(data)
|
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 import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.html import escape
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from .models import Setting
|
from .models import Setting
|
||||||
from .fields import DictField
|
from .fields import DictField
|
||||||
|
@ -24,34 +26,38 @@ def to_form_value(value):
|
||||||
data = value
|
data = value
|
||||||
return data
|
return data
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return ''
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class BaseForm(forms.Form):
|
class BaseForm(forms.Form):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
settings = Setting.objects.all()
|
db_settings = Setting.objects.all()
|
||||||
for name, field in self.fields.items():
|
for name, field in self.fields.items():
|
||||||
db_value = getattr(settings, name).value
|
db_value = getattr(db_settings, name).value
|
||||||
if db_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)
|
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:
|
if not self.is_bound:
|
||||||
raise ValueError("Form is not bound")
|
raise ValueError("Form is not bound")
|
||||||
|
|
||||||
settings = Setting.objects.all()
|
db_settings = Setting.objects.all()
|
||||||
if self.is_valid():
|
if self.is_valid():
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for name, value in self.cleaned_data.items():
|
for name, value in self.cleaned_data.items():
|
||||||
field = self.fields[name]
|
field = self.fields[name]
|
||||||
if isinstance(field.widget, forms.PasswordInput) and not value:
|
if isinstance(field.widget, forms.PasswordInput) and not value:
|
||||||
continue
|
continue
|
||||||
if value == to_form_value(getattr(settings, name).value):
|
if value == to_form_value(getattr(db_settings, name).value):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
'name': name,
|
'name': name,
|
||||||
|
'category': category,
|
||||||
'value': to_model_value(value)
|
'value': to_model_value(value)
|
||||||
}
|
}
|
||||||
Setting.objects.update_or_create(defaults=defaults, name=name)
|
Setting.objects.update_or_create(defaults=defaults, name=name)
|
||||||
|
@ -62,10 +68,10 @@ class BaseForm(forms.Form):
|
||||||
class BasicSettingForm(BaseForm):
|
class BasicSettingForm(BaseForm):
|
||||||
SITE_URL = forms.URLField(
|
SITE_URL = forms.URLField(
|
||||||
label=_("Current SITE URL"),
|
label=_("Current SITE URL"),
|
||||||
help_text="http://jumpserver.abc.com:8080"
|
help_text="eg: http://jumpserver.abc.com:8080"
|
||||||
)
|
)
|
||||||
USER_GUIDE_URL = forms.URLField(
|
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")
|
help_text=_("User first login update profile done redirect to it")
|
||||||
)
|
)
|
||||||
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
||||||
|
@ -111,7 +117,8 @@ class LDAPSettingForm(BaseForm):
|
||||||
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org'
|
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org'
|
||||||
)
|
)
|
||||||
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
|
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(
|
AUTH_LDAP_USER_ATTR_MAP = DictField(
|
||||||
label=_("User attr map"),
|
label=_("User attr map"),
|
||||||
|
@ -119,13 +126,45 @@ class LDAPSettingForm(BaseForm):
|
||||||
"username": "cn",
|
"username": "cn",
|
||||||
"name": "sn",
|
"name": "sn",
|
||||||
"email": "mail"
|
"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_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||||
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
AUTH_LDAP_START_TLS = forms.BooleanField(
|
AUTH_LDAP_START_TLS = forms.BooleanField(
|
||||||
label=_("Use SSL"), initial=False, required=False
|
label=_("Use SSL"), initial=False, required=False
|
||||||
)
|
)
|
||||||
AUTH_LDAP = forms.BooleanField(
|
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), initial=False, required=False)
|
||||||
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)
|
return JsonResponse(context)
|
||||||
|
|
||||||
|
|
||||||
class CustomFilterMixin(object):
|
class IDInFilterMixin(object):
|
||||||
def filter_queryset(self, queryset):
|
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')
|
id_list = self.request.query_params.get('id__in')
|
||||||
if id_list:
|
if id_list:
|
||||||
import json
|
import json
|
||||||
|
@ -99,9 +99,8 @@ class DatetimeSearchMixin:
|
||||||
|
|
||||||
if date_from_s:
|
if date_from_s:
|
||||||
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
||||||
self.date_from = date_from.replace(
|
tz = timezone.get_current_timezone()
|
||||||
tzinfo=timezone.get_current_timezone()
|
self.date_from = tz.localize(date_from)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.date_from = timezone.now() - timezone.timedelta(7)
|
self.date_from = timezone.now() - timezone.timedelta(7)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import json
|
||||||
|
|
||||||
import ldap
|
import ldap
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.utils import ProgrammingError, OperationalError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django_auth_ldap.config import LDAPSearch
|
from django_auth_ldap.config import LDAPSearch
|
||||||
|
@ -24,6 +25,7 @@ class SettingManager(models.Manager):
|
||||||
class Setting(models.Model):
|
class Setting(models.Model):
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
||||||
value = models.TextField(verbose_name=_("Value"))
|
value = models.TextField(verbose_name=_("Value"))
|
||||||
|
category = models.CharField(max_length=128, default="default")
|
||||||
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
|
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
|
||||||
comment = models.TextField(verbose_name=_("Comment"))
|
comment = models.TextField(verbose_name=_("Comment"))
|
||||||
|
|
||||||
|
@ -33,17 +35,28 @@ class Setting(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value_(self):
|
def cleaned_value(self):
|
||||||
try:
|
try:
|
||||||
return json.loads(self.value)
|
return json.loads(self.value)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return None
|
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
|
@classmethod
|
||||||
def refresh_all_settings(cls):
|
def refresh_all_settings(cls):
|
||||||
settings_list = cls.objects.all()
|
try:
|
||||||
for setting in settings_list:
|
settings_list = cls.objects.all()
|
||||||
setting.refresh_setting()
|
for setting in settings_list:
|
||||||
|
setting.refresh_setting()
|
||||||
|
except (ProgrammingError, OperationalError):
|
||||||
|
pass
|
||||||
|
|
||||||
def refresh_setting(self):
|
def refresh_setting(self):
|
||||||
try:
|
try:
|
||||||
|
@ -53,9 +66,9 @@ class Setting(models.Model):
|
||||||
setattr(settings, self.name, value)
|
setattr(settings, self.name, value)
|
||||||
|
|
||||||
if self.name == "AUTH_LDAP":
|
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)
|
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)
|
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
|
||||||
|
|
||||||
if self.name == "AUTH_LDAP_SEARCH_FILTER":
|
if self.name == "AUTH_LDAP_SEARCH_FILTER":
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<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,17 +73,20 @@ def to_html(s):
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def time_util_with_seconds(date_from, date_to):
|
def time_util_with_seconds(date_from, date_to):
|
||||||
if date_from and date_to:
|
if not date_from:
|
||||||
delta = date_to - date_from
|
|
||||||
seconds = delta.seconds
|
|
||||||
if seconds < 60:
|
|
||||||
return '{} s'.format(seconds)
|
|
||||||
elif seconds < 60*60:
|
|
||||||
return '{} m'.format(seconds//60)
|
|
||||||
else:
|
|
||||||
return '{} h'.format(seconds//3600)
|
|
||||||
else:
|
|
||||||
return ''
|
return ''
|
||||||
|
if not date_to:
|
||||||
|
return ''
|
||||||
|
date_to = timezone.now()
|
||||||
|
|
||||||
|
delta = date_to - date_from
|
||||||
|
seconds = delta.seconds
|
||||||
|
if seconds < 60:
|
||||||
|
return '{} s'.format(seconds)
|
||||||
|
elif seconds < 60*60:
|
||||||
|
return '{} m'.format(seconds//60)
|
||||||
|
else:
|
||||||
|
return '{} h'.format(seconds//3600)
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
@ -92,3 +95,8 @@ def is_bool_field(field):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
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'^$', views.BasicSettingView.as_view(), name='basic-setting'),
|
||||||
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
||||||
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-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):
|
if isinstance(data, str):
|
||||||
data = hashlib.md5(data.encode('utf-8'))
|
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')
|
return value.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
_STRPTIME_LOCK = threading.Lock()
|
_STRPTIME_LOCK = threading.Lock()
|
||||||
|
|
||||||
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
|
_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.shortcuts import render, redirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.translation import ugettext as _
|
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 .mixins import AdminUserRequiredMixin
|
||||||
from .signals import ldap_auth_enable
|
from .signals import ldap_auth_enable
|
||||||
|
|
||||||
|
@ -86,3 +89,34 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
|
||||||
context = self.get_context_data()
|
context = self.get_context_data()
|
||||||
context.update({"form": form})
|
context.update({"form": form})
|
||||||
return render(request, self.template_name, context)
|
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_auth_ldap.config import LDAPSearch
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
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!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = CONFIG.DEBUG or False
|
DEBUG = CONFIG.DEBUG or False
|
||||||
|
|
||||||
|
|
||||||
# Absolute url for some case, for example email link
|
# Absolute url for some case, for example email link
|
||||||
SITE_URL = CONFIG.SITE_URL or 'http://localhost'
|
SITE_URL = CONFIG.SITE_URL or 'http://localhost'
|
||||||
|
|
||||||
|
|
||||||
# LOG LEVEL
|
# LOG LEVEL
|
||||||
LOG_LEVEL = 'DEBUG' if DEBUG else CONFIG.LOG_LEVEL or 'WARNING'
|
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
|
SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN or None
|
||||||
CSRF_COOKIE_DOMAIN = CONFIG.CSRF_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'
|
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||||
# Database
|
# Database
|
||||||
|
@ -241,7 +238,7 @@ USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
# I18N translation
|
# I18N translation
|
||||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'), ]
|
LOCALE_PATHS = [os.path.join(BASE_DIR, 'i18n'), ]
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
# 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_ROOT = os.path.join(PROJECT_DIR, "data", "static")
|
||||||
STATIC_DIR = os.path.join(BASE_DIR, "static")
|
STATIC_DIR = os.path.join(BASE_DIR, "static")
|
||||||
|
|
||||||
|
|
||||||
STATICFILES_DIRS = (
|
STATICFILES_DIRS = (
|
||||||
os.path.join(BASE_DIR, "static"),
|
os.path.join(BASE_DIR, "static"),
|
||||||
)
|
)
|
||||||
|
@ -265,7 +261,7 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/'
|
||||||
# BOOTSTRAP_COLUMN_COUNT = 11
|
# BOOTSTRAP_COLUMN_COUNT = 11
|
||||||
|
|
||||||
# Init data or generate fake data source for development
|
# 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 config
|
||||||
EMAIL_HOST = CONFIG.EMAIL_HOST
|
EMAIL_HOST = CONFIG.EMAIL_HOST
|
||||||
|
@ -298,7 +294,7 @@ REST_FRAMEWORK = {
|
||||||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
||||||
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'],
|
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'],
|
||||||
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||||
'PAGE_SIZE': 15
|
# 'PAGE_SIZE': 15
|
||||||
}
|
}
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
@ -308,7 +304,6 @@ AUTHENTICATION_BACKENDS = [
|
||||||
# Custom User Auth model
|
# Custom User Auth model
|
||||||
AUTH_USER_MODEL = 'users.User'
|
AUTH_USER_MODEL = 'users.User'
|
||||||
|
|
||||||
|
|
||||||
# Auth LDAP settings
|
# Auth LDAP settings
|
||||||
AUTH_LDAP = CONFIG.AUTH_LDAP
|
AUTH_LDAP = CONFIG.AUTH_LDAP
|
||||||
AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI
|
AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI
|
||||||
|
@ -319,7 +314,7 @@ AUTH_LDAP_SEARCH_FILTER = CONFIG.AUTH_LDAP_SEARCH_FILTER
|
||||||
AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS
|
AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS
|
||||||
AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP
|
AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP
|
||||||
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||||
AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER,
|
AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER,
|
||||||
)
|
)
|
||||||
AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||||
AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
|
@ -332,7 +327,6 @@ AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
||||||
if AUTH_LDAP:
|
if AUTH_LDAP:
|
||||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
||||||
|
|
||||||
|
|
||||||
# Celery using redis as broker
|
# Celery using redis as broker
|
||||||
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
|
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
|
||||||
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
||||||
|
@ -354,7 +348,6 @@ CELERY_REDIRECT_STDOUTS = True
|
||||||
CELERY_REDIRECT_STDOUTS_LEVEL = "INFO"
|
CELERY_REDIRECT_STDOUTS_LEVEL = "INFO"
|
||||||
CELERY_WORKER_HIJACK_ROOT_LOGGER = False
|
CELERY_WORKER_HIJACK_ROOT_LOGGER = False
|
||||||
|
|
||||||
|
|
||||||
# Cache use redis
|
# Cache use redis
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
|
@ -373,7 +366,25 @@ CAPTCHA_FOREGROUND_COLOR = '#001100'
|
||||||
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
|
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
|
||||||
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE
|
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
|
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
||||||
BOOTSTRAP3 = {
|
BOOTSTRAP3 = {
|
||||||
|
@ -386,6 +397,6 @@ BOOTSTRAP3 = {
|
||||||
}
|
}
|
||||||
|
|
||||||
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
|
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
|
DEFAULT_EXPIRED_YEARS = 70
|
||||||
USER_GUIDE_URL = ""
|
USER_GUIDE_URL = ""
|
||||||
|
|
|
@ -4,16 +4,16 @@ from __future__ import unicode_literals
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
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.schemas import get_schema_view
|
||||||
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
|
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])
|
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', IndexView.as_view(), name='index'),
|
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'^users/', include('users.urls.views_urls', namespace='users')),
|
||||||
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
||||||
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
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.utils import timezone
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
@ -45,15 +48,22 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
||||||
return self.session_week.values('user').distinct().count()
|
return self.session_week.values('user').distinct().count()
|
||||||
|
|
||||||
def get_week_login_asset_count(self):
|
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):
|
def get_month_day_metrics(self):
|
||||||
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
||||||
return month_str
|
return month_str
|
||||||
|
|
||||||
def get_month_login_metrics(self):
|
def get_month_login_metrics(self):
|
||||||
return [self.session_month.filter(date_start__date=d).count()
|
data = []
|
||||||
for d in self.session_month_dates]
|
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):
|
def get_month_active_user_metrics(self):
|
||||||
if self.session_month_dates_archive:
|
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_week = Session.objects.filter(date_start__gt=week_ago)
|
||||||
self.session_month = Session.objects.filter(date_start__gt=month_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 = self.session_month.dates('date_start', 'day')
|
||||||
self.session_month_dates_archive = [
|
|
||||||
self.session_month.filter(date_start__date=d)
|
self.session_month_dates_archive = []
|
||||||
for d in self.session_month_dates
|
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 = {
|
context = {
|
||||||
'assets_count': self.get_asset_count(),
|
'assets_count': self.get_asset_count(),
|
||||||
|
@ -149,3 +167,12 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
||||||
|
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super(IndexView, self).get_context_data(**kwargs)
|
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
|
new_adhoc.become = become_info
|
||||||
|
|
||||||
if not adhoc or adhoc != new_adhoc:
|
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()
|
new_adhoc.save()
|
||||||
task.latest_adhoc = new_adhoc
|
task.latest_adhoc = new_adhoc
|
||||||
created = True
|
created = True
|
||||||
|
|
|
@ -3,17 +3,14 @@
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.views import APIView, Response
|
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 rest_framework import viewsets
|
||||||
|
|
||||||
from common.utils import get_object_or_none
|
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
|
||||||
from users.permissions import IsValidUser, IsSuperUser, IsAppUser, IsSuperUserOrAppUser
|
from .utils import NodePermissionUtil
|
||||||
from .utils import get_user_granted_assets, get_user_granted_asset_groups, \
|
from .models import NodePermission
|
||||||
get_user_asset_permissions, get_user_group_asset_permissions, \
|
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, \
|
||||||
get_user_group_granted_assets, get_user_group_granted_asset_groups
|
NodeGrantedSerializer, SystemUser, NodeSerializer
|
||||||
from .models import AssetPermission
|
|
||||||
from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, \
|
|
||||||
AssetGroup, AssetGroupGrantedSerializer, SystemUser, MyAssetGroupGrantedSerializer
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,102 +18,23 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
资产授权列表的增删改查api
|
资产授权列表的增删改查api
|
||||||
"""
|
"""
|
||||||
queryset = AssetPermission.objects.all()
|
queryset = NodePermission.objects.all()
|
||||||
serializer_class = serializers.AssetPermissionSerializer
|
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
||||||
permission_classes = (IsSuperUser,)
|
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):
|
def get_serializer_class(self):
|
||||||
if getattr(self, 'user_id', ''):
|
if self.action in ("list", 'retrieve'):
|
||||||
return serializers.UserAssetPermissionSerializer
|
return serializers.AssetPermissionListSerializer
|
||||||
return serializers.AssetPermissionSerializer
|
return self.serializer_class
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
node_id = self.request.query_params.get('node_id')
|
||||||
|
|
||||||
class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
|
if node_id:
|
||||||
"""
|
queryset = queryset.filter(node__id=node_id)
|
||||||
将用户从授权中移除,Detail页面会调用
|
|
||||||
"""
|
|
||||||
permission_classes = (IsSuperUser,)
|
|
||||||
serializer_class = serializers.AssetPermissionUpdateUserSerializer
|
|
||||||
queryset = AssetPermission.objects.all()
|
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
return queryset
|
||||||
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})
|
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedAssetsApi(ListAPIView):
|
class UserGrantedAssetsApi(ListAPIView):
|
||||||
|
@ -128,41 +46,43 @@ class UserGrantedAssetsApi(ListAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user_id = self.kwargs.get('pk', '')
|
user_id = self.kwargs.get('pk', '')
|
||||||
|
|
||||||
queryset = []
|
queryset = []
|
||||||
|
|
||||||
if user_id:
|
if user_id:
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
for k, v in get_user_granted_assets(user).items():
|
else:
|
||||||
k.system_users_granted = v
|
user = self.request.user
|
||||||
queryset.append(k)
|
|
||||||
|
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
|
return queryset
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
class UserGrantedAssetGroupsApi(APIView):
|
if self.kwargs.get('pk') is None:
|
||||||
permission_classes = (IsValidUser,)
|
self.permission_classes = (IsValidUser,)
|
||||||
|
return super().get_permissions()
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
asset_groups = {}
|
|
||||||
user_id = kwargs.get('pk', '')
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedAssetGroupsWithAssetsApi(ListAPIView):
|
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)
|
||||||
|
else:
|
||||||
|
user = self.request.user
|
||||||
|
nodes = NodePermissionUtil.get_user_nodes(user)
|
||||||
|
return nodes.keys()
|
||||||
|
|
||||||
|
|
||||||
|
class UserGrantedNodesWithAssetsApi(ListAPIView):
|
||||||
"""
|
"""
|
||||||
授权用户的资产组,注:这里的资产组并非是授权列表中授权的,
|
授权用户的资产组,注:这里的资产组并非是授权列表中授权的,
|
||||||
而是把所有资产取出来,然后反查出所有资产组,然后合并得到,
|
而是把所有资产取出来,然后反查出所有资产组,然后合并得到,
|
||||||
|
@ -171,7 +91,7 @@ class UserGrantedAssetGroupsWithAssetsApi(ListAPIView):
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "资产组1",
|
"value": "node",
|
||||||
... 其它属性
|
... 其它属性
|
||||||
"assets_granted": [
|
"assets_granted": [
|
||||||
{
|
{
|
||||||
|
@ -191,133 +111,35 @@ class UserGrantedAssetGroupsWithAssetsApi(ListAPIView):
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
serializer_class = AssetGroupGrantedSerializer
|
serializer_class = NodeGrantedSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user_id = self.kwargs.get('pk', '')
|
user_id = self.kwargs.get('pk', '')
|
||||||
|
queryset = []
|
||||||
if not user_id:
|
if not user_id:
|
||||||
return []
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = get_object_or_404(User, id=user_id)
|
||||||
|
|
||||||
user = get_object_or_404(User, id=user_id)
|
nodes = NodePermissionUtil.get_user_nodes_with_assets(user)
|
||||||
asset_groups = get_user_granted_asset_groups(user)
|
assets = {}
|
||||||
|
for k, v in NodePermissionUtil.get_user_assets(user).items():
|
||||||
queryset = []
|
if k.is_unixlike():
|
||||||
for asset_group, assets_system_users in asset_groups.items():
|
system_users_granted = [s for s in v if s.protocol == 'ssh']
|
||||||
assets = []
|
else:
|
||||||
for asset, system_users in assets_system_users:
|
system_users_granted = [s for s in v if s.protocol == 'rdp']
|
||||||
asset.system_users_granted = system_users
|
assets[k] = system_users_granted
|
||||||
assets.append(asset)
|
for node, v in nodes.items():
|
||||||
asset_group.assets_granted = assets
|
for asset in v['assets']:
|
||||||
queryset.append(asset_group)
|
asset.system_users_granted = assets[asset]
|
||||||
|
node.assets_granted = v['assets']
|
||||||
|
queryset.append(node)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
class MyGrantedAssetsApi(ListAPIView):
|
if self.kwargs.get('pk') is None:
|
||||||
"""
|
self.permission_classes = (IsValidUser,)
|
||||||
用户自己查询授权的资产列表
|
return super().get_permissions()
|
||||||
"""
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
asset.system_users_granted = assets[asset]
|
|
||||||
queryset.append(asset)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class UserGroupGrantedAssetsApi(ListAPIView):
|
class UserGroupGrantedAssetsApi(ListAPIView):
|
||||||
|
@ -326,27 +148,52 @@ class UserGroupGrantedAssetsApi(ListAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user_group_id = self.kwargs.get('pk', '')
|
user_group_id = self.kwargs.get('pk', '')
|
||||||
|
queryset = []
|
||||||
|
|
||||||
if user_group_id:
|
if not user_group_id:
|
||||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
return queryset
|
||||||
queryset = get_user_group_granted_assets(user_group)
|
|
||||||
else:
|
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||||
queryset = []
|
assets = NodePermissionUtil.get_user_group_assets(user_group)
|
||||||
|
for k, v in assets.items():
|
||||||
|
k.system_users_granted = v
|
||||||
|
queryset.append(k)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class UserGroupGrantedAssetGroupsApi(ListAPIView):
|
class UserGroupGrantedNodesApi(ListAPIView):
|
||||||
permission_classes = (IsSuperUser,)
|
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):
|
def get_queryset(self):
|
||||||
user_group_id = self.kwargs.get('pk', '')
|
user_group_id = self.kwargs.get('pk', '')
|
||||||
|
queryset = []
|
||||||
|
|
||||||
if user_group_id:
|
if not user_group_id:
|
||||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
return queryset
|
||||||
queryset = get_user_group_granted_asset_groups(user_group)
|
|
||||||
else:
|
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||||
queryset = []
|
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
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@ -363,7 +210,7 @@ class ValidateUserAssetPermissionView(APIView):
|
||||||
asset = get_object_or_404(Asset, id=asset_id)
|
asset = get_object_or_404(Asset, id=asset_id)
|
||||||
system_user = get_object_or_404(SystemUser, id=system_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, []):
|
if system_user in assets_granted.get(asset, []):
|
||||||
return Response({'msg': True}, status=200)
|
return Response({'msg': True}, status=200)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -5,3 +5,7 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
class PermsConfig(AppConfig):
|
class PermsConfig(AppConfig):
|
||||||
name = 'perms'
|
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 import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# from .hands import User, UserGroup, Asset, AssetGroup, SystemUser
|
from .models import NodePermission
|
||||||
from .models import AssetPermission
|
|
||||||
from users.models import User
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionForm(forms.ModelForm):
|
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:
|
class Meta:
|
||||||
model = AssetPermission
|
model = NodePermission
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'users', 'user_groups', 'assets', 'asset_groups',
|
'node', 'user_group', 'system_user', 'is_active',
|
||||||
'system_users', 'is_active', 'date_expired', 'comment',
|
'date_expired', 'comment',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'user_groups': forms.SelectMultiple(
|
'node': forms.Select(
|
||||||
attrs={'class': 'select2',
|
attrs={'style': 'display:none'}
|
||||||
'data-placeholder': _('Select user groups')}),
|
),
|
||||||
'assets': forms.SelectMultiple(
|
'user_group': forms.Select(
|
||||||
attrs={'class': 'select2',
|
attrs={'class': 'select2', 'data-placeholder': _("User group")}
|
||||||
'data-placeholder': _('Select assets')}),
|
),
|
||||||
'asset_groups': forms.SelectMultiple(
|
'system_user': forms.Select(
|
||||||
attrs={'class': 'select2',
|
attrs={'class': 'select2', 'data-placeholder': _('System user')}
|
||||||
'data-placeholder': _('Select asset groups')}),
|
),
|
||||||
'system_users': forms.SelectMultiple(
|
|
||||||
attrs={'class': 'select2',
|
|
||||||
'data-placeholder': _('Select system users')}),
|
|
||||||
}
|
|
||||||
help_texts = {
|
|
||||||
'name': '* required',
|
|
||||||
'system_users': '* required',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def clean_user_groups(self):
|
def clean_system_user(self):
|
||||||
users = self.cleaned_data.get('users')
|
return self.cleaned_data['system_user']
|
||||||
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']
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
from users.utils import AdminUserRequiredMixin
|
from users.utils import AdminUserRequiredMixin
|
||||||
from users.models import User, UserGroup
|
from users.models import User, UserGroup
|
||||||
from assets.models import Asset, AssetGroup, SystemUser
|
from assets.models import Asset, AssetGroup, SystemUser, Node
|
||||||
from assets.serializers import AssetGrantedSerializer, AssetGroupGrantedSerializer, MyAssetGroupGrantedSerializer
|
from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,3 +67,22 @@ class AssetPermission(models.Model):
|
||||||
if cluster_remain:
|
if cluster_remain:
|
||||||
errors[system_user] = cluster_remain
|
errors[system_user] = cluster_remain
|
||||||
return errors
|
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 django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from .models import AssetPermission
|
from common.fields import StringIDField
|
||||||
from .hands import User
|
from .models import AssetPermission, NodePermission
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionSerializer(serializers.ModelSerializer):
|
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||||
assets_ = serializers.SerializerMethodField()
|
class Meta:
|
||||||
asset_groups_ = serializers.SerializerMethodField()
|
model = NodePermission
|
||||||
users_ = serializers.SerializerMethodField()
|
fields = [
|
||||||
user_groups_ = serializers.SerializerMethodField()
|
'id', 'node', 'user_group', 'system_user',
|
||||||
system_users_ = serializers.SerializerMethodField()
|
'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:
|
class Meta:
|
||||||
model = AssetPermission
|
model = NodePermission
|
||||||
fields = '__all__'
|
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):
|
class AssetPermissionUpdateUserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
@ -54,7 +41,7 @@ class AssetPermissionUpdateAssetSerializer(serializers.ModelSerializer):
|
||||||
fields = ['id', 'assets']
|
fields = ['id', 'assets']
|
||||||
|
|
||||||
|
|
||||||
class UserAssetPermissionSerializer(AssetPermissionSerializer):
|
class UserAssetPermissionCreateUpdateSerializer(AssetPermissionCreateUpdateSerializer):
|
||||||
is_inherited = serializers.SerializerMethodField()
|
is_inherited = serializers.SerializerMethodField()
|
||||||
|
|
||||||
@staticmethod
|
@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__)
|
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.assets_selected = {};
|
||||||
jumpserver.groups_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2.asset').select2()
|
$('.select2.asset').select2()
|
||||||
|
@ -206,11 +206,11 @@ $(document).ready(function () {
|
||||||
$('.select2.group').select2()
|
$('.select2.group').select2()
|
||||||
.on('select2:select', function(evt) {
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
jumpserver.groups_selected[data.id] = data.text;
|
jumpserver.nodes_selected[data.id] = data.text;
|
||||||
})
|
})
|
||||||
.on('select2:unselect', function(evt) {
|
.on('select2:unselect', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
delete jumpserver.groups_selected[data.id]
|
delete jumpserver.nodes_selected[data.id]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on('click', '.btn-add-assets', function () {
|
.on('click', '.btn-add-assets', function () {
|
||||||
|
@ -232,7 +232,7 @@ $(document).ready(function () {
|
||||||
removeAssets(assets)
|
removeAssets(assets)
|
||||||
})
|
})
|
||||||
.on('click', '#btn-add-group', function () {
|
.on('click', '#btn-add-group', function () {
|
||||||
if (Object.keys(jumpserver.groups_selected).length === 0) {
|
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ $(document).ready(function () {
|
||||||
return $(this).data('gid');
|
return $(this).data('gid');
|
||||||
}).get();
|
}).get();
|
||||||
|
|
||||||
$.map(jumpserver.groups_selected, function(group_name, index) {
|
$.map(jumpserver.nodes_selected, function(group_name, index) {
|
||||||
groups.push(index);
|
groups.push(index);
|
||||||
$('#opt_' + index).remove();
|
$('#opt_' + index).remove();
|
||||||
$('.group_edit tbody').append(
|
$('.group_edit tbody').append(
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'Create asset permission ' %}</h5>
|
<h5>{{ action }}</h5>
|
||||||
<div class="ibox-tools">
|
<div class="ibox-tools">
|
||||||
<a class="collapse-link">
|
<a class="collapse-link">
|
||||||
<i class="fa fa-chevron-up"></i>
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
@ -28,19 +28,23 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ibox-content">
|
<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="" >
|
<form method="post" class="form-horizontal" action="" >
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<h3>{% trans 'Name' %}</h3>
|
<h3>{% trans 'Basic' %}</h3>
|
||||||
{% bootstrap_field form.name layout="horizontal" %}
|
<div class="form-group">
|
||||||
<div class="hr-line-dashed"></div>
|
<label class="col-md-2 control-label" for="id_name">{% trans 'Node' %}</label>
|
||||||
<h3>{% trans 'User' %}</h3>
|
<div class="col-md-9">
|
||||||
{% bootstrap_field form.users layout="horizontal" %}
|
<input type="text" class="form-control" readonly value="{{ form.node.initial }}">
|
||||||
{% bootstrap_field form.user_groups layout="horizontal" %}
|
</div>
|
||||||
<div class="hr-line-dashed"></div>
|
</div>
|
||||||
<h3>{% trans 'Asset' %}</h3>
|
{{ form.node }}
|
||||||
{% bootstrap_field form.assets layout="horizontal" %}
|
{% bootstrap_field form.user_group layout="horizontal" %}
|
||||||
{% bootstrap_field form.asset_groups layout="horizontal" %}
|
{% bootstrap_field form.system_user layout="horizontal" %}
|
||||||
{% bootstrap_field form.system_users layout="horizontal" %}
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Other' %}</h3>
|
<h3>{% trans 'Other' %}</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -59,7 +63,7 @@
|
||||||
</div>
|
</div>
|
||||||
<span class="help-block ">{{ form.date_expired.errors }}</span>
|
<span class="help-block ">{{ form.date_expired.errors }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% bootstrap_field form.comment layout="horizontal" %}
|
{% bootstrap_field form.comment layout="horizontal" %}
|
||||||
|
|
||||||
<div class="form-group">
|
<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