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] 修改bug
pull/1053/head^2
老广 2018-03-07 21:21:56 +08:00 committed by GitHub
parent 5c8dd5676c
commit c2abd58dcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
242 changed files with 9447 additions and 5502 deletions

View File

@ -1,7 +1,7 @@
[简述你的问题] [简述你的问题]
##### 使用版本 ##### 使用版本
[请提供你使用的Jumpserver版本 0.3.2 或 0.4.0] [请提供你使用的Jumpserver版本 0.3.2 或 0.5.0]
##### 问题复现步骤 ##### 问题复现步骤
1. [步骤1] 1. [步骤1]

3
.gitignore vendored
View File

@ -29,4 +29,5 @@ media
celerybeat.pid celerybeat.pid
django.db django.db
celerybeat-schedule.db celerybeat-schedule.db
static data/static
_build/

View File

@ -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 开发者文档

View File

@ -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"})

View File

@ -0,0 +1,5 @@
from .admin_user import *
from .asset import *
from .label import *
from .system_user import *
from .node import *

View File

@ -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"})

112
apps/assets/api/asset.py Normal file
View File

@ -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)

38
apps/assets/api/label.py Normal file
View File

@ -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)

119
apps/assets/api/node.py Normal file
View File

@ -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))

View File

@ -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"})

View File

@ -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()

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
#
from .asset import *
from .label import *
from .user import *

133
apps/assets/forms/asset.py Normal file
View File

@ -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

View File

@ -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

135
apps/assets/forms/user.py Normal file
View File

@ -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'),
}

View File

@ -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

View File

@ -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 *

View File

@ -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:

View File

@ -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'))

View File

@ -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')

119
apps/assets/models/node.py Normal file
View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
#
from .asset import *
from .admin_user import *
from .label import *
from .system_user import *
from .node import *

View File

@ -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']

View File

@ -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",
)

View File

@ -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())

View File

@ -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])

View File

@ -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()

View File

@ -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')

View File

@ -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'])

View File

@ -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)

View File

@ -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)

View File

@ -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">

View File

@ -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 %}

View File

@ -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();
}); });

View File

@ -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) {

View File

@ -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>

View File

@ -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 %}

View File

@ -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>');

View File

@ -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 %}

View File

@ -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 }}";

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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">

View File

@ -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 %}

View File

@ -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">

View File

@ -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 %}

View File

@ -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>');

View File

@ -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">

View File

@ -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()
}; };

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
#

View File

@ -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()]

View File

@ -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

View File

@ -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'),
] ]

View File

@ -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

View File

@ -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 *

View File

@ -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 = {

View File

@ -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:

View File

@ -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')

View File

@ -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')

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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__()}

View File

@ -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"
)
)

View File

@ -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)

View File

@ -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":

View File

@ -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">

View File

@ -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">

View File

@ -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">

View File

@ -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 %}

View File

@ -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)

View File

@ -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'),
] ]

View File

@ -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"

View File

@ -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.

View File

@ -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 = ""

View File

@ -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')),

View File

@ -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是单独部署的一个程序你需要部署lunacoco配置nginx做url分发,
如果你看到了这个页面证明你访问的不是nginx监听的端口祝你好运
"""
return HttpResponse(msg)

Binary file not shown.

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -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']

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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