Unverified Commit c2abd58d authored by 老广's avatar 老广 Committed by GitHub

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
parent 5c8dd567
[简述你的问题]
##### 使用版本
[请提供你使用的Jumpserver版本 0.3.2 或 0.4.0]
[请提供你使用的Jumpserver版本 0.3.2 或 0.5.0]
##### 问题复现步骤
1. [步骤1]
......
......@@ -29,4 +29,5 @@ media
celerybeat.pid
django.db
celerybeat-schedule.db
static
data/static
_build/
......@@ -5,10 +5,18 @@
[![Ansible](https://img.shields.io/badge/ansible-2.2.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.1.2-green.svg?style=plastic)](http://www.paramiko.org/)
Jumpserver is a open source proxy server, developed by `Python` and `Django`, aim to help
companies to efficiently user, assets, authority and audit management
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理
----
Jumpserver是全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
改变世界,从一点点开始。
----
### Feature 功能
- Auth 统一认证
......@@ -24,9 +32,16 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互
* Python 3.6
* Django 1.11
### Install 安装
### 快速启动
   [详细安装](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
```
$ docker run -p 8080:80 -p 2222:2222 jumpserver/jumpserver:0.5.0-beta2
```
更多见 [Dockerfile](https://github.com/jumpserver/Dockerfile.git)
### 详细安装步骤
   [文档](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
### Usage 使用
......@@ -70,6 +85,11 @@ demo使用了开发者模式,并发只能为1
参见 https://github.com/jumpserver/jumpserver/milestone/2
### SDK
- python: https://github.com/jumpserver/jumpserver-python-sdk
- java: https://github.com/KaiJunYan/jumpserver-java-sdk.git
### Docs 开发者文档
......
This diff is collapsed.
from .admin_user import *
from .asset import *
from .label import *
from .system_user import *
from .node import *
# ~*~ 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"})
\ No newline at end of file
# -*- 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)
\ No newline at end of file
# ~*~ 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)
# ~*~ 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))
# ~*~ 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"})
\ No newline at end of file
This diff is collapsed.
# -*- coding: utf-8 -*-
#
from .asset import *
from .label import *
from .user import *
# -*- 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
# -*- 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
# -*- 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'),
}
\ No newline at end of file
......@@ -6,13 +6,12 @@
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2017 by Jumpserver Team.
:copyright: (c) 2014-2018 by Jumpserver Team.
:license: GPL v2, see LICENSE for more details.
"""
from users.utils import AdminUserRequiredMixin
from users.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from common.mixins import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from users.models import User, UserGroup
from perms.utils import get_user_granted_assets
from perms.tasks import push_users
\ No newline at end of file
from perms.utils import NodePermissionUtil
......@@ -2,7 +2,9 @@
# -*- coding: utf-8 -*-
#
from .user import AdminUser, SystemUser
from .label import Label
from .cluster import *
from .group import *
from .node import *
from .asset import *
from .utils import *
......@@ -28,46 +28,36 @@ def default_cluster():
return cluster.id
class Asset(models.Model):
# Todo: Move them to settings
STATUS_CHOICES = (
('In use', _('In use')),
('Out of use', _('Out of use')),
)
TYPE_CHOICES = (
('Server', _('Server')),
('VM', _('VM')),
('Switch', _('Switch')),
('Router', _('Router')),
('Firewall', _('Firewall')),
('Storage', _("Storage")),
)
ENV_CHOICES = (
('Prod', _('Production')),
('Dev', _('Development')),
('Test', _('Testing')),
)
def default_node():
try:
from .node import Node
return Node.root()
except:
return None
class Asset(models.Model):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster'))
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),)
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),)
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True, default='In use', verbose_name=_('Asset status'))
# Auth
admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user"))
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote control card IP'))
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect
......@@ -82,12 +72,13 @@ class Asset(models.Model):
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
platform = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Platform'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
......@@ -104,6 +95,12 @@ class Asset(models.Model):
return True, ''
return False, warning
def is_unixlike(self):
if self.platform not in ("Windows", "Other"):
return True
else:
return False
@property
def hardware_info(self):
if self.cpu_count:
......@@ -116,36 +113,20 @@ class Asset(models.Model):
@property
def is_connective(self):
if not self.is_unixlike():
return True
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
if val == 1:
return True
else:
return False
@property
def admin_user_avail(self):
if self.admin_user:
admin_user = self.admin_user
elif self.cluster and self.cluster.admin_user:
admin_user = self.cluster.admin_user
else:
return None
return admin_user
@property
def is_has_private_admin_user(self):
if self.admin_user:
return True
else:
return False
def to_json(self):
return {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'groups': [group.name for group in self.groups.all()],
}
def _to_secret_json(self):
......@@ -156,13 +137,14 @@ class Asset(models.Model):
Todo: May be move to ops implements it
"""
data = self.to_json()
if self.admin_user_avail:
admin_user = self.admin_user_avail
if self.admin_user:
admin_user = self.admin_user
data.update({
'username': admin_user.username,
'password': admin_user.password,
'private_key': admin_user.private_key_file,
'become': admin_user.become_info,
'groups': [node.value for node in self.nodes.all()],
})
return data
......@@ -181,7 +163,6 @@ class Asset(models.Model):
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
hostname=forgery_py.internet.user_name(True),
admin_user=choice(AdminUser.objects.all()),
cluster=choice(Cluster.objects.all()),
port=22,
created_by='Fake')
try:
......
......@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
class AssetGroup(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
......
# -*- 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')
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
__all__ = ['Node']
class Node(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, unique=True, verbose_name=_("Value"))
child_mark = models.IntegerField(default=0)
date_create = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.value
@property
def name(self):
return self.value
@property
def full_value(self):
if self == self.__class__.root():
return self.value
else:
return '{}/{}'.format(self.value, self.parent.full_value)
@property
def level(self):
return len(self.key.split(':'))
def get_next_child_key(self):
mark = self.child_mark
self.child_mark += 1
self.save()
return "{}:{}".format(self.key, mark)
def create_child(self, value):
child_key = self.get_next_child_key()
child = self.__class__.objects.create(key=child_key, value=value)
return child
def get_children(self):
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
def get_all_children(self):
return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
def get_family(self):
children = list(self.get_all_children())
children.append(self)
return children
def get_assets(self):
from .asset import Asset
assets = Asset.objects.filter(nodes__id=self.id)
return assets
def get_active_assets(self):
return self.get_assets().filter(is_active=True)
def get_all_assets(self):
from .asset import Asset
if self.is_root():
assets = Asset.objects.all()
else:
nodes = self.get_family()
assets = Asset.objects.filter(nodes__in=nodes)
return assets
def get_all_active_assets(self):
return self.get_all_assets().filter(is_active=True)
def is_root(self):
return self.key == '0'
@property
def parent(self):
if self.key == "0":
return self.__class__.root()
elif not self.key.startswith("0"):
return self.__class__.root()
parent_key = ":".join(self.key.split(":")[:-1])
try:
parent = self.__class__.objects.get(key=parent_key)
except Node.DoesNotExist:
return self.__class__.root()
else:
return parent
@parent.setter
def parent(self, parent):
self.key = parent.get_next_child_key()
@property
def ancestor(self):
if self.parent == self.__class__.root():
return [self.__class__.root()]
else:
return [self.parent, *tuple(self.parent.ancestor)]
@property
def ancestor_with_node(self):
ancestor = self.ancestor
ancestor.insert(0, self)
return ancestor
@classmethod
def root(cls):
obj, created = cls.objects.get_or_create(
key='0', defaults={"key": '0', 'value': "ROOT"}
)
return obj
......@@ -26,14 +26,14 @@ signer = get_signer()
class AssetUser(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=16, verbose_name=_('Username'))
username = models.CharField(max_length=128, verbose_name=_('Username'))
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
@property
def password(self):
......@@ -175,15 +175,12 @@ class AdminUser(AssetUser):
return info
def get_related_assets(self):
assets = []
for cluster in self.cluster_set.all():
assets.extend(cluster.assets.all())
assets.extend(self.asset_set.all())
return list(set(assets))
assets = self.asset_set.all()
return assets
@property
def assets_amount(self):
return len(self.get_related_assets())
return self.get_related_assets().count()
class Meta:
ordering = ['name']
......@@ -212,11 +209,13 @@ class AdminUser(AssetUser):
class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
)
cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster"))
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
......@@ -226,21 +225,6 @@ class SystemUser(AssetUser):
def __str__(self):
return self.name
def get_clusters_assets(self):
from .asset import Asset
clusters = self.get_clusters()
return Asset.objects.filter(cluster__in=clusters)
def get_clusters(self):
return self.cluster.all()
def get_clusters_joined(self):
return ', '.join([cluster.name for cluster in self.get_clusters()])
@property
def assets_amount(self):
return len(self.get_clusters_assets())
def to_json(self):
return {
'id': self.id,
......@@ -251,6 +235,13 @@ class SystemUser(AssetUser):
'auto_push': self.auto_push,
}
@property
def assets(self):
assets = set()
for node in self.nodes.all():
assets.update(set(node.get_all_assets()))
return assets
@property
def assets_connective(self):
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
......@@ -264,6 +255,12 @@ class SystemUser(AssetUser):
def reachable_assets(self):
return self.assets_connective.get('contacted', [])
def is_need_push(self):
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL:
return True
else:
return False
class Meta:
ordering = ['name']
verbose_name = _("System user")
......
# -*- 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)
# -*- coding: utf-8 -*-
#
from .asset import *
from .admin_user import *
from .label import *
from .system_user import *
from .node import *
# -*- 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']
# -*- 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",
)
# -*- 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())
# -*- 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])
# -*- 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()
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')
\ No newline at end of file
......@@ -3,4 +3,3 @@
from django.dispatch import Signal
on_app_ready = Signal()
on_system_user_auth_changed = Signal(providing_args=['system_user'])
# -*- coding: utf-8 -*-
#
from django.db.models.signals import post_save, post_init
from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver
from django.utils.translation import gettext as _
from common.utils import get_logger
from .models import Asset, SystemUser, Cluster
from .models import Asset, SystemUser, Node
from .tasks import update_assets_hardware_info_util, \
test_asset_connectability_util, \
push_system_user_util
test_asset_connectability_util, push_system_user_to_node, \
push_node_system_users_to_asset
logger = get_logger(__file__)
......@@ -25,105 +24,48 @@ def test_asset_conn_on_created(asset):
test_asset_connectability_util.delay(asset)
def push_cluster_system_users_to_asset(asset):
if asset.cluster:
logger.info("Push cluster system user to asset: {}".format(asset))
task_name = _("Push cluster system users to asset")
system_users = asset.cluster.systemuser_set.all()
push_system_user_util.delay(system_users, [asset], task_name)
def set_asset_root_node(asset):
logger.debug("Set asset default node: {}".format(Node.root()))
asset.nodes.add(Node.root())
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_created(sender, instance=None, created=False, **kwargs):
if instance and created:
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
set_asset_root_node(instance)
if created:
logger.info("Asset `{}` create signal received".format(instance))
update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance)
push_cluster_system_users_to_asset(instance)
@receiver(post_init, sender=Asset)
def on_asset_init(sender, instance, created=False, **kwargs):
if instance and created is False:
instance.__original_cluster = instance.cluster
@receiver(post_save, sender=Asset)
def on_asset_cluster_changed(sender, instance=None, created=False, **kwargs):
if instance and created is False and instance.cluster != instance.__original_cluster:
logger.info("Asset cluster changed signal received")
push_cluster_system_users_to_asset(instance)
def push_to_cluster_assets_on_system_user_created_or_update(system_user):
if not system_user.auto_push:
return
logger.debug("Push system user `{}` to cluster assets".format(system_user.name))
for cluster in system_user.cluster.all():
task_name = _("Push system user to cluster assets: {}->{}").format(
cluster.name, system_user.name
)
assets = cluster.assets.all()
push_system_user_util.delay([system_user], assets, task_name)
@receiver(post_save, sender=SystemUser)
def on_system_user_created_or_updated(sender, instance=None, **kwargs):
if instance and instance.auto_push:
logger.info("System user `{}` create or update signal received".format(instance))
push_to_cluster_assets_on_system_user_created_or_update(instance)
@receiver(post_init, sender=Cluster, dispatch_uid="my_unique_identifier")
def on_cluster_init(sender, instance, **kwargs):
instance.__original_assets = tuple(instance.assets.values_list('pk', flat=True))
instance.__origin_system_users = tuple(instance.systemuser_set.values_list('pk', flat=True))
@receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier")
def on_cluster_assets_changed(sender, instance, **kwargs):
assets_origin = instance.__original_assets
assets_new = instance.assets.values_list('pk', flat=True)
assets_added = set(assets_new) - set(assets_origin)
if assets_added:
logger.debug("Receive cluster change assets signal")
logger.debug("Push cluster `{}` system users to: {}".format(
instance, ', '.join([str(asset) for asset in assets_added])
))
assets = []
for asset_id in assets_added:
try:
asset = Asset.objects.get(pk=asset_id)
except Asset.DoesNotExist:
continue
else:
assets.append(asset)
system_users = [s for s in instance.systemuser_set.all() if s.auto_push]
task_name = _("Push system user to assets")
push_system_user_util.delay(system_users, assets, task_name)
@receiver(post_save, sender=Cluster, dispatch_uid="my_unique_identifier2")
def on_cluster_system_user_changed(sender, instance, **kwargs):
system_users_origin = instance.__origin_system_users
system_user_new = instance.systemuser_set.values_list('pk', flat=True)
system_users_added = set(system_user_new) - set(system_users_origin)
if system_users_added:
logger.debug("Receive cluster change system users signal")
system_users = []
for system_user_id in system_users_added:
try:
system_user = SystemUser.objects.get(pk=system_user_id)
except SystemUser.DoesNotExist:
continue
else:
system_users.append(system_user)
logger.debug("Push new system users `{}` to cluster `{}` assets".format(
','.join([s.name for s in system_users]), instance
))
task_name = _(
"Push system user to cluster assets: {}->{}").format(
instance.name, ', '.join(s.name for s in system_users)
)
push_system_user_util.delay(system_users, instance.assets.all(), task_name)
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
def on_system_user_update(sender, instance=None, created=True, **kwargs):
if instance and not created:
for node in instance.nodes.all():
push_system_user_to_node(instance, node)
@receiver(m2m_changed, sender=SystemUser.nodes.through)
def on_system_user_node_change(sender, instance=None, **kwargs):
if instance and kwargs["action"] == "post_add":
for pk in kwargs['pk_set']:
node = kwargs['model'].objects.get(pk=pk)
push_system_user_to_node(instance, node)
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs):
if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
logger.debug("Asset node change signal received")
for pk in kwargs['pk_set']:
node = kwargs['model'].objects.get(pk=pk)
push_node_system_users_to_asset(node, [instance])
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_node_assets_changed(sender, instance=None, **kwargs):
if isinstance(instance, Node) and kwargs['action'] == 'post_add':
logger.debug("Node assets change signal received")
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
push_node_system_users_to_asset(instance, assets)
This diff is collapsed.
......@@ -7,7 +7,6 @@
{% load bootstrap3 %}
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
<form method="post" class="form-horizontal" action="" id="fm_asset_group_bulk_update">
<div class="form-group">
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
<div class="col-sm-9" id="select2-container">
......
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_id %}asset_list_modal{% endblock %}
{#{% block modal_title%}{% trans "Please select assets" %}{% endblock %}#}
{% block modal_body %}
{#<div class="btn-group" style="float: right">#}
{# <button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>#}
{# <ul class="dropdown-menu labels">#}
{# {% for label in labels %}#}
{# <li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>#}
{# {% endfor %}#}
{# </ul>#}
{#</div>#}
<table class="table table-striped table-bordered table-hover " id="asset_modal_table" width="100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
<script>
var modal_table;
function initModalTable() {
var options = {
ele: $('#asset_modal_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
$(td).html(rowData.hardware_info)
}},
{targets: 4, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 5, createdCell: function (td, cellData) {
if (cellData === 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "cpu_cores"}, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
],
op_html: $('#actions').html()
};
modal_table = jumpserver.initServerSideDataTable(options);
return modal_table;
}
$(document).ready(function(){
initModalTable();
}).on('click', '#btn_select_assets', function () {
var data_table = $('#asset_modal_table').DataTable();
var id_list = [];
data_table.rows({selected: true}).every(function(){
id_list.push(this.data().id);
});
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
return
}
var data = {
'assets': id_list
};
var success = function () {
modal_table.ajax.reload()
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/add/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
})
</script>
{% endblock %}
{% block modal_confirm_id %}btn_select_assets{% endblock %}
......@@ -13,7 +13,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{% trans 'Create system user' %}</h5>
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
......@@ -39,7 +39,6 @@
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.cluster layout="horizontal" %}
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
......@@ -52,8 +51,8 @@
</div>
</div>
<div class="auth-fields">
{% bootstrap_field form.private_key_file layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
</div>
<div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
......@@ -82,6 +81,14 @@
{% block custom_foot_js %}
<script>
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var password_id = '#' + '{{ form.password.id_for_label }}';
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
var shell_id = '#' + '{{ form.shell.id_for_label }}';
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
function authFieldsDisplay() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
......@@ -89,9 +96,23 @@
$('.auth-fields').removeClass('hidden');
}
}
function protocolChange() {
if ($(protocol_id).attr('value') === 'rdp') {
$.each(need_change_field, function (index, value) {
$(value).addClass('hidden')
});
$(password_id).removeClass('hidden')
} else {
$.each(need_change_field, function (index, value) {
$(value).removeClass('hidden')
});
}
}
$(document).ready(function () {
$('.select2').select2();
authFieldsDisplay();
protocolChange();
$(auto_generate_key).change(function () {
authFieldsDisplay();
});
......
......@@ -20,14 +20,6 @@
<li class="active">
<a href="{% url 'assets:admin-user-assets' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Assets list' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-admin-user">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
......@@ -59,7 +51,6 @@
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Reachable' %}</th>
</tr>
</thead>
......@@ -109,7 +100,7 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
{targets: 4, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
......@@ -117,8 +108,9 @@ function initTable() {
}
}}],
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "type" }, {data: "is_connective" }],
columns: [
{data: function(){return ""}}, {data: "hostname" }, {data: "ip" },
{data: "port" }, {data: "is_connective" }],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
......@@ -127,14 +119,6 @@ function initTable() {
$(document).ready(function () {
initTable();
})
.on('click', '.btn-delete-admin-user', function () {
var $this = $(this);
var name = "{{ admin_user.name }}";
var uid = "{{ admin_user.id }}";
var the_url = '{% url "api-assets:admin-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'assets:admin-user-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
var error = function (data) {
......
......@@ -13,7 +13,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{% trans 'Create admin user' %}</h5>
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
......
......@@ -31,7 +31,7 @@
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ admin_user.name }}</b></span>
......@@ -77,11 +77,10 @@
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-info">
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Using this as cluster admin user' %}
<i class="fa fa-info-circle"></i> {% trans 'Replace node assets admin user with this' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="table-clusters">
......@@ -89,25 +88,19 @@
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select cluster' %}" id="cluster_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for cluster in cluster_remain %}
<option value="{{ cluster.id }}" id="opt_{{ cluster.id }}" >{{ cluster.name }}</option>
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.value }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-add-cluster">{% trans 'Confirm' %}</button>
<button type="button" class="btn btn-primary btn-sm" id="btn-change-admin-user">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for cluster in admin_user.cluster_set.all %}
<tr>
<td ><b class="bdg_cluster" data-gid={{ cluster.id }}>{{ cluster.name }}</b></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
......@@ -123,29 +116,20 @@
{% endblock %}
{% block custom_foot_js %}
<script>
function bindToCluster(clusters) {
var the_url = "{% url 'api-assets:admin-user-add-clusters' pk=admin_user.id %}";
function replaceNodeAssetsAdminUser(nodes) {
var the_url = "{% url 'api-assets:replace-nodes-admin-user' pk=admin_user.id %}";
var body = {
clusters: clusters
nodes: nodes
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#cluster_selected').val('');
$.map(jumpserver.cluster_selected, function(cluster_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('#table-clusters tbody').append(
'<tr>' +
'<td><b class="bdg_cluster" data-gid="' + index + '">' + cluster_name + '</b></td>' +
'</tr>'
)
});
$.map(jumpserver.cluster_selected, function(value, index) {
$('#nodes_selected').val('');
$.map(jumpserver.nodes_selected, function(value, index) {
$('#opt_' + index).remove();
});
// clear jumpserver.groups_selected
jumpserver.cluster_selected = {};
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
......@@ -154,15 +138,15 @@ function bindToCluster(clusters) {
});
}
jumpserver.cluster_selected = {};
jumpserver.nodes_selected = {};
$(document).ready(function () {
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.cluster_selected[data.id] = data.text;
jumpserver.nodes_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.cluster_selected[data.id]
delete jumpserver.nodes_selected[data.id]
});
})
.on('click', '.btn-delete-admin-user', function () {
......@@ -173,15 +157,15 @@ $(document).ready(function () {
var redirect_url = "{% url 'assets:admin-user-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
.on('click', '#btn-add-cluster', function () {
if (Object.keys(jumpserver.cluster_selected).length === 0) {
.on('click', '#btn-change-admin-user', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var clusters = [];
$.map(jumpserver.cluster_selected, function(value, index) {
clusters.push(index);
var nodes = [];
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
bindToCluster(clusters)
replaceNodeAssetsAdminUser(nodes);
})
</script>
{% endblock %}
......@@ -5,7 +5,7 @@
{% block help_message %}
<div class="alert alert-info help-message">
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。可以设置主机级别管理用户,也设置集群级别管理用户,这样资产可以不用再单独设置
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
</div>
{% endblock %}
......@@ -74,7 +74,8 @@ $(document).ready(function(){
if (val === 100) {
innerHtml = "<span class='text-navy'>" + val + "% </span>";
} else {
innerHtml = "<span class='text-danger'>" + val + "% </span>";
var num = new Number(val);
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
......
......@@ -2,6 +2,8 @@
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load asset_tags %}
{% load common_tags %}
{% block form %}
<form action="" method="post" class="form-horizontal">
......@@ -15,25 +17,48 @@
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.cluster layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.env layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Group' %}</h3>
{% bootstrap_field form.groups layout="horizontal" %}
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Labels' %}</h3>
<div class="form-group {% if form.errors.labels %} has-error {% endif %}">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Select labels' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}">
{% for label in labels %}
{% if label in form.labels.initial %}
<option value="{{ label.id }}" selected>{{ label.value }}</option>
{% else %}
<option value="{{ label.id }}">{{ label.value }}</option>
{% endif %}
{% endfor %}
</optgroup>
{% endfor %}
</select>
{% if form.errors.labels %}
{% for e in form.errors.labels %}
<div class="help-block">{{ e }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.comment layout="horizontal" %}
{% bootstrap_field form.is_active layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
......@@ -45,11 +70,20 @@
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
})
</script>
<script>
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
{% endblock %}
\ No newline at end of file
This diff is collapsed.
{% 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 %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">分配/回收资产</h4>
</div>
<div class="modal-body" style="padding-bottom: 0px;">
<table aria-describedby="editable_info" role="grid" class="table table-striped table-bordered table-hover dataTable" id="editable">
<thead>
<tr>
<th class="text-center" style="background-color:white">
<input type="checkbox" id="check_all" onclick="checkAll()">
</th>
<th id="th_no">id</th>
<th>资产名称</th>
<th>IP</th>
<th>类型</th>
</tr>
</thead>
<tbody>
{% for asset in assets %}
{% if asset.id in all_assets %}
<tr name="oAssets" class="odd selected text-center">
<td class="text-center" ><input type="checkbox" name="checked" value="{{ asset.id }}" checked="checked"></td>
{% else %}
<tr name="oAssets">
<td class="text-center"><input type="checkbox" name="checked" value="{{ asset.id }}" ></td>
{% endif %}
<td class="text-center">{{ asset.id }}</td>
<td class="text-center">{{ asset.hostname }}</td>
<td class="text-center">{{ asset.ip }}</td>
<td class="text-center">{{ asset.env }}-{{ asset.type }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="close-btn">取消</button>
<button type="button" class="btn btn-primary" id="save-btn">保存</button>
</div>
<script type="text/javascript">
$(document).ready(function(){
var table = $('#editable').DataTable({
"aLengthMenu": [[10, 25, 50, -1], ["10", "25", "50", "all"]],
"iDisplayLength":25,
"aaSorting": [[2, "asc"]],
"aoColumnDefs": [ { "bSortable": false, "aTargets": [ 0 ] }],
"bAutoWidth": false,
columns: [
{data: "checkbox"},
{data: "id"},
{data: "hostname"},
{data: "ip"},
{data: "type"}
]
});
//将ID列隐藏
table.column('1').visible(false);
$('#editable tbody').on( 'click', 'tr', function () {
//alert($(this).hasClass('selected'));
if($(this).hasClass('selected')){
$(this).removeClass('selected');
this.children[0].children[0].checked=0;
}else{
$(this).addClass('selected');
this.children[0].children[0].checked=1;
}
});
$('#close-btn').on('click',function(){
$('#modal').modal('hide');
});
var size_name = document.getElementById('asset_on_count').innerText;
$('#save-btn').on('click',function(){
//alert( table.rows('.selected').data().length +' row(s) selected' );
var d = table.rows('.selected').data();
var size = d.length;
var re = /\d+/;
document.getElementById('add_asset').value = size;
var str= size_name;
var re=/\d+/g;
document.getElementById('asset_on_count').innerText = str.replace(re, size);
var column2 = table.rows('.selected').data();
$("#asset_sed").find("input[name='assets']").remove();
$("#asset_sed").find("button[name='asset_hostname']").remove();
for(var i=0;i<column2.length;i++){
column2[i].checkbox='<input name="checked" value="1" checked="" type="checkbox">';
var value = column2[i].id;
var ip = column2[i].ip;
var hostname = column2[i].hostname;
$("#asset_sed").append("<input type='hidden' name='assets' value='"+value+"'>");
$("#asset_on_p").append("<button name='asset_hostname' title='"+ip+"' type='button' class='btn btn-default btn-xs ss'>"+hostname+"</button> ");
}
$('#modal').modal('hide');
});
}); //$(document).ready
var bCheck = 1;
function checkAll(){
if(bCheck){
$("tr[name='oAssets']").each(function(){
oCheckbox = this.children[0].children[0];
$(this).toggleClass('selected',true);
oCheckbox.checked=1;
});
document.getElementById('check_all').checked=1;
bCheck = 0;
}else{
$("tr[name='oAssets']").each(function(){
oCheckbox = this.children[0].children[0];
$(this).toggleClass('selected',false);
oCheckbox.checked=0;
});
document.getElementById('check_all').checked=0;
bCheck = 1;
}
}
</script>
......@@ -2,6 +2,8 @@
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load asset_tags %}
{% load common_tags %}
{% block custom_head_css_js_create %}
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
......@@ -20,32 +22,44 @@
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.cluster layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.env layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Group' %}</h3>
{% bootstrap_field form.groups layout="horizontal" %}
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Configuration' %}</h3>
{% bootstrap_field form.number layout="horizontal" %}
{% bootstrap_field form.remote_card_ip layout="horizontal" %}
<h3>{% trans 'Labels' %}</h3>
<div class="form-group">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}">
{% for label in labels %}
{% if label in form.labels.initial %}
<option value="{{ label.id }}" selected>{{ label.value }}</option>
{% else %}
<option value="{{ label.id }}">{{ label.value }}</option>
{% endif %}
{% endfor %}
</optgroup>
{% endfor %}
</select>
</div>
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Location' %}</h3>
{% bootstrap_field form.cabinet_no layout="horizontal" %}
{% bootstrap_field form.cabinet_pos layout="horizontal" %}
<h3>{% trans 'Configuration' %}</h3>
{% bootstrap_field form.number layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.status layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
{% bootstrap_field form.is_active layout="horizontal" %}
......@@ -62,14 +76,18 @@
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$("#tags").select2({
tags: true,
maximumSelectionLength: 8 //最多能够选择的个数
});
})
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
{% endblock %}
\ No newline at end of file
This diff is collapsed.
{% 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 %}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
......@@ -7,8 +7,8 @@
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.value layout="horizontal" %}
{% bootstrap_field form.assets layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
......
This diff is collapsed.
......@@ -21,9 +21,6 @@
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
......
......@@ -5,8 +5,8 @@
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.private_key_file layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
<div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8">
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -10,4 +10,5 @@ urlpatterns = [
url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'),
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment