From 97b240cfdd9f5ce694da2255280ab7972ab90ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Wed, 18 Jul 2018 14:55:16 +0800 Subject: [PATCH 1/9] =?UTF-8?q?Revert=20"=E6=8E=88=E6=9D=83=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=88=86=E9=A1=B5=E9=97=AE=E9=A2=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api.py | 4 ---- apps/perms/templates/perms/asset_permission_list.html | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/perms/api.py b/apps/perms/api.py index 090f1988a..40366a19b 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -5,7 +5,6 @@ from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView from rest_framework import viewsets -from rest_framework.pagination import LimitOffsetPagination from common.utils import set_or_append_attr_bulk, get_object_or_none from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser @@ -20,12 +19,9 @@ class AssetPermissionViewSet(viewsets.ModelViewSet): """ 资产授权列表的增删改查api """ - filter_fields = ("name",) - search_fields = filter_fields queryset = AssetPermission.objects.all() serializer_class = serializers.AssetPermissionCreateUpdateSerializer permission_classes = (IsSuperUser,) - pagination_class = LimitOffsetPagination def get_serializer_class(self): if self.action in ("list", 'retrieve'): diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index 2f0f74312..c18f12224 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -208,7 +208,7 @@ function initTable() { select: {}, op_html: $('#actions').html() }; - table = jumpserver.initServerSideDataTable(options); + table = jumpserver.initDataTable(options); return table } From 16b23a37fe1452528a68cf7c0d9299a5f22d9e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 24 Jul 2018 22:09:58 -0500 Subject: [PATCH 2/9] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=20(#1566)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] gunicorn不使用eventlet * [Update] 添加eventlet * 替换淘宝IP查询接口 * [Feature] 添加命令记录下载功能 (#1559) * [Feature] 添加命令记录下载功能 * [Update] 文案修改,导出记录、提交,取消全部命令导出 * [Update] 命令导出,修复时间问题 * [Update] paramiko => 2.4.1 * [Update] 修改settings * [Update] 修改权限判断 --- apps/assets/models/asset.py | 2 +- apps/common/mixins.py | 5 +- apps/i18n/zh/LC_MESSAGES/django.mo | Bin 36944 -> 37020 bytes apps/i18n/zh/LC_MESSAGES/django.po | 93 ++++++++-------- apps/jumpserver/settings.py | 1 + apps/perms/api.py | 10 +- .../templates/terminal/command_list.html | 53 ++++++--- .../templates/terminal/command_report.html | 103 ++++++++++++++++++ apps/terminal/urls/views_urls.py | 1 + apps/terminal/views/command.py | 48 +++++++- apps/users/utils.py | 8 +- jms | 2 +- requirements/requirements.txt | 2 +- 13 files changed, 249 insertions(+), 79 deletions(-) create mode 100644 apps/terminal/templates/terminal/command_report.html diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 7a2b3fe57..b26c50216 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -154,7 +154,7 @@ class Asset(models.Model): return False, warning def is_unixlike(self): - if self.platform not in ("Windows",): + if self.platform not in ("Windows", "Windows2016"): return True else: return False diff --git a/apps/common/mixins.py b/apps/common/mixins.py index 243ee93c6..ae2ae6f65 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -93,7 +93,7 @@ class DatetimeSearchMixin: date_format = '%Y-%m-%d' date_from = date_to = None - def get(self, request, *args, **kwargs): + def get_date_range(self): date_from_s = self.request.GET.get('date_from') date_to_s = self.request.GET.get('date_to') @@ -113,6 +113,9 @@ class DatetimeSearchMixin: ) else: self.date_to = timezone.now() + + def get(self, request, *args, **kwargs): + self.get_date_range() return super().get(request, *args, **kwargs) diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index 50e22bf55f237bf8d2d1e9ddf1c548523ff9daf9..bc860621d21695678c0861851093bcc830d23c74 100644 GIT binary patch delta 12395 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 diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index 9e3766748..b0ef3043f 100644 --- a/apps/i18n/zh/LC_MESSAGES/django.po +++ b/apps/i18n/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-13 19:20+0800\n" +"POT-Creation-Date: 2018-07-19 18:29+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -445,7 +445,7 @@ msgstr "默认资产组" #: terminal/templates/terminal/session_list.html:71 users/forms.py:282 #: users/models/user.py:31 users/models/user.py:333 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:13 users/views/user.py:361 +#: users/templates/users/user_group_list.html:13 users/views/user.py:367 msgid "User" msgstr "用户" @@ -685,6 +685,7 @@ msgstr "重置" #: common/templates/common/security_setting.html:71 #: common/templates/common/terminal_setting.html:108 #: perms/templates/perms/asset_permission_create_update.html:70 +#: terminal/templates/terminal/command_list.html:103 #: terminal/templates/terminal/session_list.html:126 #: terminal/templates/terminal/terminal_update.html:48 #: users/templates/users/_user.html:47 @@ -814,7 +815,7 @@ msgstr "选择节点" #: users/templates/users/user_detail.html:374 #: users/templates/users/user_detail.html:399 #: users/templates/users/user_detail.html:422 -#: users/templates/users/user_detail.html:458 +#: users/templates/users/user_detail.html:466 #: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_list.html:86 #: users/templates/users/user_list.html:200 @@ -1005,7 +1006,7 @@ msgstr "存在资产,不能删除" #: assets/templates/assets/system_user_list.html:134 #: users/templates/users/user_detail.html:369 #: users/templates/users/user_detail.html:394 -#: users/templates/users/user_detail.html:453 +#: users/templates/users/user_detail.html:461 #: users/templates/users/user_group_list.html:81 #: users/templates/users/user_list.html:195 msgid "Are you sure?" @@ -2002,7 +2003,7 @@ msgstr "文档" #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:343 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:349 msgid "Profile" msgstr "个人信息" @@ -2059,13 +2060,13 @@ msgstr "关闭" #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95 -#: users/views/login.py:330 users/views/login.py:388 users/views/user.py:65 -#: users/views/user.py:80 users/views/user.py:102 users/views/user.py:175 -#: users/views/user.py:330 users/views/user.py:380 users/views/user.py:415 +#: users/views/login.py:332 users/views/login.py:390 users/views/user.py:67 +#: users/views/user.py:82 users/views/user.py:104 users/views/user.py:180 +#: users/views/user.py:336 users/views/user.py:386 users/views/user.py:421 msgid "Users" msgstr "用户管理" -#: templates/_nav.html:13 users/views/user.py:66 +#: templates/_nav.html:13 users/views/user.py:68 msgid "User list" msgstr "用户列表" @@ -2093,7 +2094,7 @@ msgstr "命令记录" msgid "Web terminal" msgstr "Web终端" -#: templates/_nav.html:51 terminal/views/command.py:47 +#: templates/_nav.html:51 terminal/views/command.py:49 #: terminal/views/session.py:75 terminal/views/session.py:93 #: terminal/views/session.py:115 terminal/views/terminal.py:31 #: terminal/views/terminal.py:46 terminal/views/terminal.py:58 @@ -2202,13 +2203,17 @@ msgstr "参数" msgid "Goto" msgstr "转到" +#: terminal/templates/terminal/command_list.html:99 +msgid "Export command" +msgstr "导出命令" + #: terminal/templates/terminal/session_detail.html:17 #: terminal/views/session.py:116 msgid "Session detail" msgstr "会话详情" #: terminal/templates/terminal/session_detail.html:28 -#: terminal/views/command.py:48 +#: terminal/views/command.py:50 msgid "Command list" msgstr "命令记录列表" @@ -2324,7 +2329,7 @@ msgid "" "You should use your ssh client tools connect terminal: {}

{}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api.py:221 users/templates/users/login.html:50 +#: users/api.py:226 users/templates/users/login.html:50 msgid "Log in frequently and try again later" msgstr "登录频繁, 稍后重试" @@ -2725,7 +2730,7 @@ msgid "Setting" msgstr "设置" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:16 users/views/user.py:80 +#: users/templates/users/user_list.html:16 users/views/user.py:82 msgid "Create user" msgstr "创建用户" @@ -2734,7 +2739,7 @@ msgid "Reset link will be generated and sent to the user. " msgstr "生成重置密码连接,通过邮件发送给用户" #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:176 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:181 msgid "User detail" msgstr "用户详情" @@ -2772,7 +2777,7 @@ msgid "Send reset ssh key mail" msgstr "发送重置密钥邮件" #: users/templates/users/user_detail.html:186 -#: users/templates/users/user_detail.html:444 +#: users/templates/users/user_detail.html:446 msgid "Unblock user" msgstr "解除登录限制" @@ -2818,7 +2823,7 @@ msgstr "更新ssh密钥成功" msgid "User SSH public key update" msgstr "ssh密钥" -#: users/templates/users/user_detail.html:454 +#: users/templates/users/user_detail.html:462 msgid "After unlocking the user, the user can log in normally." msgstr "解除用户登录限制后,此用户即可正常登录" @@ -2878,8 +2883,8 @@ msgstr "用户删除失败" msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:116 users/views/user.py:205 -#: users/views/user.py:259 +#: users/templates/users/user_profile.html:116 users/views/user.py:211 +#: users/views/user.py:265 msgid "User groups" msgstr "用户组" @@ -2925,7 +2930,7 @@ msgid "" "corresponding private key." msgstr "新的公钥已设置成功,请下载对应的私钥" -#: users/templates/users/user_update.html:4 users/views/user.py:103 +#: users/templates/users/user_update.html:4 users/views/user.py:105 msgid "Update user" msgstr "更新用户" @@ -3079,104 +3084,104 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:75 +#: users/views/login.py:76 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:178 users/views/user.py:500 users/views/user.py:525 +#: users/views/login.py:180 users/views/user.py:506 users/views/user.py:531 msgid "MFA code invalid" msgstr "MFA码认证失败" -#: users/views/login.py:207 +#: users/views/login.py:209 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:208 +#: users/views/login.py:210 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:224 +#: users/views/login.py:226 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:237 +#: users/views/login.py:239 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:238 +#: users/views/login.py:240 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:251 +#: users/views/login.py:253 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:252 +#: users/views/login.py:254 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:273 users/views/login.py:286 +#: users/views/login.py:275 users/views/login.py:288 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:282 +#: users/views/login.py:284 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:292 users/views/user.py:118 users/views/user.py:398 +#: users/views/login.py:294 users/views/user.py:120 users/views/user.py:404 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:330 +#: users/views/login.py:332 msgid "First login" msgstr "首次登陆" -#: users/views/login.py:389 +#: users/views/login.py:391 msgid "Login log list" msgstr "登录日志" -#: users/views/user.py:129 +#: users/views/user.py:131 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:234 +#: users/views/user.py:240 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:331 +#: users/views/user.py:337 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:362 +#: users/views/user.py:368 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:381 +#: users/views/user.py:387 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:416 +#: users/views/user.py:422 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:457 +#: users/views/user.py:463 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:551 +#: users/views/user.py:557 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:552 +#: users/views/user.py:558 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:554 +#: users/views/user.py:560 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:555 +#: users/views/user.py:561 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index c0a536276..547a9ac3d 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -336,6 +336,7 @@ AUTH_LDAP_GROUP_SEARCH = LDAPSearch( AUTH_LDAP_CONNECTION_OPTIONS = { ldap.OPT_TIMEOUT: 5 } +AUTH_LDAP_GROUP_CACHE_TIMEOUT = 1 AUTH_LDAP_ALWAYS_UPDATE_USER = True AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend' diff --git a/apps/perms/api.py b/apps/perms/api.py index 40366a19b..2c0a35e85 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -72,10 +72,7 @@ class UserGrantedAssetsApi(ListAPIView): util = AssetPermissionUtil(user) for k, v in util.get_assets().items(): - if k.is_unixlike(): - system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']] - else: - system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']] + system_users_granted = [s for s in v if s.protocol == k.protocol] k.system_users_granted = system_users_granted queryset.append(k) return queryset @@ -123,10 +120,7 @@ class UserGrantedNodesWithAssetsApi(ListAPIView): for node, _assets in nodes.items(): assets = _assets.keys() for k, v in _assets.items(): - if k.is_unixlike(): - system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']] - else: - system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']] + system_users_granted = [s for s in v if s.protocol == k.protocol] k.system_users_granted = system_users_granted node.assets_granted = assets queryset.append(node) diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html index 6b55d787e..50daf682d 100644 --- a/apps/terminal/templates/terminal/command_list.html +++ b/apps/terminal/templates/terminal/command_list.html @@ -92,27 +92,52 @@ {% endfor %} + +
+
+ +
+ +
+
+
{% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/terminal/templates/terminal/command_report.html b/apps/terminal/templates/terminal/command_report.html new file mode 100644 index 000000000..3542c1423 --- /dev/null +++ b/apps/terminal/templates/terminal/command_report.html @@ -0,0 +1,103 @@ +{% load common_tags %} +{% load static %} + + + + + Command Report + + + + +
+
+

Command Report

+
+

total: {{ total_count }}

+

date: {{ now | ts_to_date }}

+
+ +
+ +
+ {% for command in queryset %} +
+

+ [{{ command.user}} {{ command.system_user }}@{{ command.asset }} {{ command.timestamp | ts_to_date }}] + {{ forloop.counter }} +

+ +

$ {{ command.input }}

+ +
{{ command.output }}
+
+ +
+ {% endfor %} +
+
+
+ + \ No newline at end of file diff --git a/apps/terminal/urls/views_urls.py b/apps/terminal/urls/views_urls.py index 834d0f39d..c5865cb2b 100644 --- a/apps/terminal/urls/views_urls.py +++ b/apps/terminal/urls/views_urls.py @@ -24,5 +24,6 @@ urlpatterns = [ # Command view url(r'^command/$', views.CommandListView.as_view(), name='command-list'), + url(r'^command/export/$', views.CommandExportView.as_view(), name='command-export') ] diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 748261414..5e12b7ea4 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -1,17 +1,19 @@ # -*- coding: utf-8 -*- # -from django.views.generic import ListView +from django.views.generic import ListView, View from django.conf import settings -from django.utils import timezone from django.utils.translation import ugettext as _ +from django.http import HttpResponse +from django.template import loader +import time from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from ..models import Command from .. import utils from ..backends import get_multi_command_storage -__all__ = ['CommandListView'] +__all__ = ['CommandListView', 'CommandExportView'] common_storage = get_multi_command_storage() @@ -60,7 +62,43 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView): return super().get_context_data(**kwargs) +class CommandExportView(DatetimeSearchMixin, AdminUserRequiredMixin, View): + model = Command + command = user = asset = system_user = action = '' + date_from = date_to = None + def get(self, request, *args, **kwargs): + queryset = self.get_queryset() + template = 'terminal/command_report.html' + context = { + 'queryset': queryset, + 'total_count': len(queryset), + 'now': time.time(), + } + content = loader.render_to_string(template, context, request) + content_type = 'application/octet-stream' + response = HttpResponse(content, content_type) + filename = 'command-report-{}.html'.format(int(time.time())) + response['Content-Disposition'] = 'attachment; filename="%s"' % filename + return response - - + def get_queryset(self): + self.get_date_range() + self.action = self.request.GET.get('action', '') + self.command = self.request.GET.get('command', '') + self.user = self.request.GET.get("user", '') + self.asset = self.request.GET.get('asset', '') + self.system_user = self.request.GET.get('system_user', '') + filter_kwargs = dict() + filter_kwargs['date_from'] = self.date_from + filter_kwargs['date_to'] = self.date_to + if self.user: + filter_kwargs['user'] = self.user + if self.asset: + filter_kwargs['asset'] = self.asset + if self.system_user: + filter_kwargs['system_user'] = self.system_user + if self.command: + filter_kwargs['input'] = self.command + queryset = common_storage.filter(**filter_kwargs) + return queryset diff --git a/apps/users/utils.py b/apps/users/utils.py index 7cbaa75f0..047cf8e71 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -212,10 +212,10 @@ def write_login_log(*args, **kwargs): def get_ip_city(ip, timeout=10): - # Taobao ip api: http://ip.taobao.com//service/getIpInfo.php?ip=8.8.8.8 + # Taobao ip api: http://ip.taobao.com/service/getIpInfo.php?ip=8.8.8.8 # Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=json - url = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=%s&format=json' % ip + url = 'http://ip.taobao.com/service/getIpInfo.php?ip=%s' % ip try: r = requests.get(url, timeout=timeout) except: @@ -224,8 +224,8 @@ def get_ip_city(ip, timeout=10): if r and r.status_code == 200: try: data = r.json() - if not isinstance(data, int) and data['ret'] == 1: - city = data['country'] + ' ' + data['city'] + if not isinstance(data, int) and data['code'] == 0: + city = data['data']['country'] + ' ' + data['data']['city'] except ValueError: pass return city diff --git a/jms b/jms index 47eb81859..5e334c926 100755 --- a/jms +++ b/jms @@ -122,8 +122,8 @@ def start_gunicorn(): cmd = [ 'gunicorn', 'jumpserver.wsgi', '-b', bind, - '-w', str(WORKERS), '-k', 'eventlet', + '-w', str(WORKERS), '--access-logformat', log_format, '-p', pid_file, ] diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 25d28f259..b0cdc31f3 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -46,7 +46,7 @@ MarkupSafe==1.0 mysqlclient==1.3.12 olefile==0.44 openapi-codec==1.3.2 -paramiko==2.4.0 +paramiko==2.4.1 passlib==1.7.1 Pillow==4.3.0 pyasn1==0.4.2 From d5451a482ad81ab1f277e362c6df19188fafa649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 6 Aug 2018 23:34:35 -0500 Subject: [PATCH 3/9] Dev (#1646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 添加org * [Update] 修改url * [Update] 完成基本框架 * [Update] 修改一些逻辑 * [Update] 修改用户view * [Update] 修改资产 * [Update] 修改asset api * [Update] 修改协议小问题 * [Update] stash it * [Update] 修改约束 * [Update] 修改外键为org_id * [Update] 删掉Premiddleware * [Update] 修改Node * [Update] 修改get_current_org 为 proxy对象 current_org * [Bugfix] 解决Node.root() 死循环,移动AdminRequired到permission中 (#1571) * [Update] 修改permission (#1574) * Tmp org (#1579) * [Update] 添加org api, 升级到django 2.0 * [Update] fix some bug * [Update] 修改一些bug * [Update] 添加授权规则org (#1580) * [Update] 修复创建授权规则,显示org_name不是有效UUID的bug * [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug; * Tmp org (#1583) * [Update] 修改一些内容 * [Update] 修改datatable 支持process * [Bugfix] 修复asset queryset 没有valid方法的bug * [Update] 在线/历史/命令model添加org;修复命令记录保存org失败bug (#1584) * [Update] 修复创建授权规则,显示org_name不是有效UUID的bug * [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug; * [Update] 在线/历史/命令model添加org * [Bugfix] 修复命令记录,保存org不成功bug * [Update] Org功能修改 * [Bugfix] 修复merge带来的问题 * [Update] org admin显示资产详情右侧选项卡;修复资产授权添加用户,会显示其他org用户的bug (#1594) * [Bugfix] 修复资产授权添加用户,显示其他org的用户bug * [Update] org admin 显示资产详情右侧选项卡 * Tmp org (#1596) * [Update] 修改index view * [Update] 修改nav * [Update] 修改profile * [Bugfix] 修复org下普通用户打开web终端看不到已被授权的资产和节点bug * [Update] 修改get_all_assets * [Bugfix] 修复节点前面有个空目录 * [Bugfix] 修复merge引起的bug * [Update] Add init * [Update] Node get_all_assets 过滤游离资产,条件nodes_key=None -> nodes=None * [Update] 恢复原来的api地址 * [Update] 修改api * [Bugfix] 修复org下用户查看我的资产不显示已授权节点/资产的bug * [Bugfix] Fix perm name unique * [Bugfix] 修复校验失败api * [Update] Merge with org * [Merge] 修改一下bug * [Update] 暂时修改一些url * [Update] 修改url 为django 2.0 path * [Update] 优化datatable 和显示组织优化 * [Update] 升级url * [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode(… (#1613) * [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode() method的bug * [Bugfix] (task任务系统)修复资产连接性测试、硬件刷新和系统用户连接性测试失败等bug * [Bugfix] 修复一些bug * [Bugfix] 修复一些bug * [Update] 更新org下普通用户的资产详情 (#1619) * [Update] 更新org下普通用户查看资产详情,只显示数据 * [Update] 优化org下普通用户查看资产详情前端代码 * [Update] 创建/更新用户的role选项;密码强度提示信息中英文; (#1623) * [Update] 修改 超级管理员/组织管理员 在 创建/更新 用户时role的选项 问题 * [Update] 用户密码强度提示信息支持中英文 * [Update] 修改token返回 * [Update] Asset返回org name * [Update] 修改支持xpack * [Update] 修改url * [Bugfix] 修复不登录就能查看资产的bug * [Update] 用户修改 * [Bugfix] ... * [Bugfix] 修复跳转错误的问题 * [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; (#1644) * [Update] 更新xpack下orgs的翻译信息 * [Update] 更新model Label,继承OrgModelMixin; * [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; * [Bugfix] 修复小bug * [Update] 优化一些api * [Update] 优化用户资产页面 * [Update] 更新 xpack/orgs 删除功能:限制在当前org下删除当前org (#1645) * [Update] 修改版本号 --- .gitignore | 1 + apps/__init__.py | 2 +- apps/assets/api/admin_user.py | 10 +- apps/assets/api/asset.py | 67 +- apps/assets/api/domain.py | 11 +- apps/assets/api/label.py | 9 +- apps/assets/api/node.py | 77 +- apps/assets/api/system_user.py | 11 +- apps/assets/forms/asset.py | 13 +- apps/assets/forms/domain.py | 3 +- apps/assets/hands.py | 4 +- apps/assets/models/asset.py | 38 +- apps/assets/models/base.py | 5 +- apps/assets/models/domain.py | 7 +- apps/assets/models/label.py | 5 +- apps/assets/models/node.py | 113 ++- apps/assets/models/user.py | 2 + apps/assets/serializers/admin_user.py | 2 +- apps/assets/serializers/asset.py | 10 +- apps/assets/serializers/node.py | 24 +- .../templates/assets/_asset_list_modal.html | 9 +- .../assets/_user_asset_detail_modal.html | 24 + .../assets/templates/assets/asset_detail.html | 2 +- apps/assets/templates/assets/asset_list.html | 83 +- apps/assets/templates/assets/domain_list.html | 4 +- .../templates/assets/user_asset_list.html | 81 +- apps/assets/urls/api_urls.py | 92 +-- apps/assets/urls/views_urls.py | 70 +- apps/assets/views/admin_user.py | 2 +- apps/assets/views/asset.py | 8 +- apps/assets/views/domain.py | 2 +- apps/assets/views/label.py | 2 +- apps/assets/views/system_user.py | 2 +- apps/audits/api.py | 4 +- apps/audits/models.py | 4 +- apps/audits/urls/api_urls.py | 3 +- apps/audits/urls/view_urls.py | 5 +- apps/audits/views.py | 3 +- apps/common/api.py | 19 +- apps/common/fields.py | 2 +- apps/common/mixins.py | 10 +- apps/common/permissions.py | 61 +- apps/common/urls/api_urls.py | 8 +- apps/common/utils.py | 124 ++- apps/common/views.py | 8 +- apps/i18n/zh/LC_MESSAGES/django.mo | Bin 37020 -> 37547 bytes apps/i18n/zh/LC_MESSAGES/django.po | 732 +++++++++++------- apps/jumpserver/settings.py | 57 +- apps/jumpserver/urls.py | 111 ++- apps/jumpserver/views.py | 19 +- apps/ops/api.py | 14 +- apps/ops/apps.py | 4 + apps/ops/hands.py | 2 - apps/ops/models/adhoc.py | 3 +- apps/ops/urls/api_urls.py | 12 +- apps/ops/urls/view_urls.py | 21 +- apps/ops/views.py | 2 +- apps/orgs/__init__.py | 0 apps/orgs/admin.py | 3 + apps/orgs/api.py | 14 + apps/orgs/apps.py | 5 + apps/orgs/context_processor.py | 15 + apps/orgs/middleware.py | 16 + apps/orgs/migrations/__init__.py | 0 apps/orgs/mixins.py | 104 +++ apps/orgs/models.py | 103 +++ apps/orgs/serializers.py | 10 + apps/orgs/tests.py | 3 + apps/orgs/urls/__init__.py | 2 + apps/orgs/urls/api_urls.py | 16 + apps/orgs/urls/views_urls.py | 14 + apps/orgs/utils.py | 47 ++ apps/orgs/views.py | 30 + apps/perms/api.py | 41 +- apps/perms/forms.py | 14 +- apps/perms/hands.py | 2 +- apps/perms/models.py | 18 +- .../perms/asset_permission_list.html | 42 +- apps/perms/urls/api_urls.py | 85 +- apps/perms/urls/views_urls.py | 15 +- apps/perms/utils.py | 4 +- apps/perms/views.py | 19 +- apps/static/css/jumpserver.css | 4 +- apps/static/img/header-profile.png | Bin 0 -> 5877 bytes apps/static/js/jumpserver.js | 123 ++- apps/templates/_footer.html | 2 +- apps/templates/_header_bar.html | 2 +- apps/templates/_left_side_bar.html | 2 +- apps/templates/_nav.html | 31 +- apps/templates/_user_profile.html | 34 +- apps/terminal/api.py | 22 +- apps/terminal/backends/command/db.py | 4 +- apps/terminal/backends/command/models.py | 4 +- apps/terminal/backends/command/serializers.py | 1 + apps/terminal/hands.py | 5 +- apps/terminal/models.py | 3 +- .../templates/terminal/session_list.html | 7 +- apps/terminal/urls/api_urls.py | 36 +- apps/terminal/urls/views_urls.py | 24 +- apps/terminal/views/command.py | 3 +- apps/terminal/views/session.py | 2 +- apps/terminal/views/terminal.py | 2 +- apps/users/api.py | 37 +- apps/users/forms.py | 77 +- apps/users/hands.py | 1 + apps/users/models/group.py | 7 +- apps/users/models/user.py | 21 +- apps/users/permissions.py | 52 -- apps/users/serializers.py | 4 +- .../users/templates/users/login_log_list.html | 7 +- .../users/templates/users/reset_password.html | 12 +- .../templates/users/user_granted_asset.html | 43 +- .../users/user_otp_authentication.html | 2 +- .../templates/users/user_password_update.html | 12 +- apps/users/templates/users/user_update.html | 12 +- apps/users/urls/api_urls.py | 39 +- apps/users/urls/views_urls.py | 68 +- apps/users/views/group.py | 3 +- apps/users/views/login.py | 19 +- apps/users/views/user.py | 16 +- requirements/requirements.txt | 5 +- 121 files changed, 2192 insertions(+), 1201 deletions(-) create mode 100644 apps/assets/templates/assets/_user_asset_detail_modal.html create mode 100644 apps/orgs/__init__.py create mode 100644 apps/orgs/admin.py create mode 100644 apps/orgs/api.py create mode 100644 apps/orgs/apps.py create mode 100644 apps/orgs/context_processor.py create mode 100644 apps/orgs/middleware.py create mode 100644 apps/orgs/migrations/__init__.py create mode 100644 apps/orgs/mixins.py create mode 100644 apps/orgs/models.py create mode 100644 apps/orgs/serializers.py create mode 100644 apps/orgs/tests.py create mode 100644 apps/orgs/urls/__init__.py create mode 100644 apps/orgs/urls/api_urls.py create mode 100644 apps/orgs/urls/views_urls.py create mode 100644 apps/orgs/utils.py create mode 100644 apps/orgs/views.py create mode 100755 apps/static/img/header-profile.png delete mode 100644 apps/users/permissions.py diff --git a/.gitignore b/.gitignore index 488f8a776..b75f0c9ff 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ django.db celerybeat-schedule.db data/static docs/_build/ +xpack diff --git a/apps/__init__.py b/apps/__init__.py index be40e1dd2..c84997cac 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -2,4 +2,4 @@ # -*- coding: utf-8 -*- # -__version__ = "1.3.3" +__version__ = "1.4.0" diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index 968cd6594..7048ce461 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -20,7 +20,7 @@ from rest_framework_bulk import BulkModelViewSet from common.mixins import IDInFilterMixin from common.utils import get_logger -from ..hands import IsSuperUser +from ..hands import IsOrgAdmin from ..models import AdminUser, Asset from .. import serializers from ..tasks import test_admin_user_connectability_manual @@ -39,19 +39,19 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): """ queryset = AdminUser.objects.all() serializer_class = serializers.AdminUserSerializer - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) class AdminUserAuthApi(generics.UpdateAPIView): queryset = AdminUser.objects.all() serializer_class = serializers.AdminUserAuthSerializer - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) class ReplaceNodesAdminUserApi(generics.UpdateAPIView): queryset = AdminUser.objects.all() serializer_class = serializers.ReplaceNodeAdminUserSerializer - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) def update(self, request, *args, **kwargs): admin_user = self.get_object() @@ -75,7 +75,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView): Test asset admin user connectivity """ queryset = AdminUser.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) def retrieve(self, request, *args, **kwargs): admin_user = self.get_object() diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 8c1f3d726..e2ce1b62a 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -2,8 +2,9 @@ # import random +import time -from rest_framework import generics +from rest_framework import generics, permissions from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView @@ -13,7 +14,7 @@ from django.db.models import Q from common.mixins import IDInFilterMixin from common.utils import get_logger -from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser +from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser from ..models import Asset, SystemUser, AdminUser, Node from .. import serializers from ..tasks import update_asset_hardware_info_manual, \ @@ -39,38 +40,42 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer pagination_class = LimitOffsetPagination - permission_classes = (IsSuperUserOrAppUser,) + permission_classes = (permissions.AllowAny,) - def get_queryset(self): - queryset = super().get_queryset()\ - .prefetch_related('labels', 'nodes')\ - .select_related('admin_user') - admin_user_id = self.request.query_params.get('admin_user_id') + def filter_node(self): node_id = self.request.query_params.get("node_id") + if not node_id: + return + + node = get_object_or_404(Node, id=node_id) show_current_asset = self.request.query_params.get("show_current_asset") - 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 and show_current_asset: - node = get_object_or_404(Node, id=node_id) - if node.is_root(): - queryset = queryset.filter( + if node.is_root(): + if show_current_asset: + self.queryset = self.queryset.filter( Q(nodes=node_id) | Q(nodes__isnull=True) ).distinct() - else: - queryset = queryset.filter(nodes=node).distinct() + return + if show_current_asset: + self.queryset = self.queryset.filter(nodes=node).distinct() + else: + self.queryset = self.queryset.filter( + nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key), + ).distinct() - if node_id and not show_current_asset: - node = get_object_or_404(Node, id=node_id) - if node.is_root(): - queryset = Asset.objects.all() - else: - queryset = queryset.filter( - nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key), - ).distinct() - return queryset + def filter_admin_user_id(self): + admin_user_id = self.request.query_params.get('admin_user_id') + if admin_user_id: + admin_user = get_object_or_404(AdminUser, id=admin_user_id) + self.queryset = self.queryset.filter(admin_user=admin_user) + + def get_queryset(self): + self.queryset = super().get_queryset()\ + .prefetch_related('labels', 'nodes')\ + .select_related('admin_user') + self.filter_admin_user_id() + self.filter_node() + return self.queryset class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): @@ -79,7 +84,7 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): """ queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) class AssetRefreshHardwareApi(generics.RetrieveAPIView): @@ -88,7 +93,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView): """ queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') @@ -102,7 +107,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): Test asset admin user connectivity """ queryset = Asset.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') @@ -113,7 +118,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): class AssetGatewayApi(generics.RetrieveAPIView): queryset = Asset.objects.all() - permission_classes = (IsSuperUserOrAppUser,) + permission_classes = (IsOrgAdminOrAppUser,) def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 5114b5561..37bebfb84 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -2,12 +2,11 @@ from rest_framework_bulk import BulkModelViewSet from rest_framework.views import APIView, Response -from rest_framework.generics import RetrieveAPIView from django.views.generic.detail import SingleObjectMixin from common.utils import get_logger -from ..hands import IsSuperUser, IsSuperUserOrAppUser +from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser from ..models import Domain, Gateway from ..utils import test_gateway_connectability from .. import serializers @@ -19,7 +18,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"] class DomainViewSet(BulkModelViewSet): queryset = Domain.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) serializer_class = serializers.DomainSerializer def get_serializer_class(self): @@ -29,7 +28,7 @@ class DomainViewSet(BulkModelViewSet): def get_permissions(self): if self.request.query_params.get('gateway'): - self.permission_classes = (IsSuperUserOrAppUser,) + self.permission_classes = (IsOrgAdminOrAppUser,) return super().get_permissions() @@ -37,12 +36,12 @@ class GatewayViewSet(BulkModelViewSet): filter_fields = ("domain",) search_fields = filter_fields queryset = Gateway.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) serializer_class = serializers.GatewaySerializer class GatewayTestConnectionApi(SingleObjectMixin, APIView): - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) model = Gateway object = None diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py index 858834d0a..e5391c76a 100644 --- a/apps/assets/api/label.py +++ b/apps/assets/api/label.py @@ -17,7 +17,7 @@ from rest_framework_bulk import BulkModelViewSet from django.db.models import Count from common.utils import get_logger -from ..hands import IsSuperUser +from ..hands import IsOrgAdmin from ..models import Label from .. import serializers @@ -27,8 +27,7 @@ __all__ = ['LabelViewSet'] class LabelViewSet(BulkModelViewSet): - queryset = Label.objects.annotate(asset_count=Count("assets")) - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) serializer_class = serializers.LabelSerializer def list(self, request, *args, **kwargs): @@ -36,3 +35,7 @@ class LabelViewSet(BulkModelViewSet): self.serializer_class = serializers.LabelDistinctSerializer self.queryset = self.queryset.values("name").distinct() return super().list(request, *args, **kwargs) + + def get_queryset(self): + self.queryset = Label.objects.annotate(asset_count=Count("assets")) + return self.queryset diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index e5ace021e..515f1f13c 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -13,16 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from rest_framework import generics, mixins +from rest_framework import generics, mixins, viewsets from rest_framework.serializers import ValidationError 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 django.shortcuts import get_object_or_404 +from django.db.models import Count from common.utils import get_logger, get_object_or_none -from ..hands import IsSuperUser +from ..hands import IsOrgAdmin from ..models import Node from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util from .. import serializers @@ -30,57 +31,31 @@ from .. import serializers logger = get_logger(__file__) __all__ = [ - 'NodeViewSet', 'NodeChildrenApi', - 'NodeAssetsApi', - 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', - 'NodeReplaceAssetsApi', + 'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi', + 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', 'TestNodeConnectiveApi' ] -class NodeViewSet(BulkModelViewSet): +class NodeViewSet(viewsets.ModelViewSet): queryset = Node.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer + def get_queryset(self): + queryset = super().get_queryset().annotate(Count('assets')) + return queryset + def perform_create(self, serializer): child_key = Node.root().get_next_child_key() serializer.validated_data["key"] = child_key serializer.save() -# class NodeWithAssetsApi(generics.ListAPIView): -# permission_classes = (IsSuperUser,) -# serializers = serializers.NodeSerializer -# -# def get_node(self): -# pk = self.kwargs.get('pk') or self.request.query_params.get('node') -# if not pk: -# node = Node.root() -# else: -# node = get_object_or_404(Node, pk) -# return node -# -# def get_queryset(self): -# queryset = [] -# node = self.get_node() -# children = node.get_children() -# assets = node.get_assets() -# queryset.extend(list(children)) -# -# for asset in assets: -# node = Node() -# node.id = asset.id -# node.parent = node.id -# node.value = asset.hostname -# queryset.append(node) -# return queryset - - class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): queryset = Node.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer instance = None @@ -126,22 +101,26 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): query_all = self.request.query_params.get("all") query_assets = self.request.query_params.get('assets') node = self.get_object() + if node is None: node = Node.root() + node.assets__count = node.get_all_assets().count() queryset.append(node) - if query_all: - children = node.get_all_children() - else: - children = node.get_children() + if query_all: + children = node.get_all_children().annotate(Count("assets")) + else: + children = node.get_children().annotate(Count("assets")) queryset.extend(list(children)) + if query_assets: assets = node.get_assets() for asset in assets: node_fake = Node() + node_fake.assets__count = 0 node_fake.id = asset.id node_fake.is_node = False - node_fake.parent_id = node.id + node_fake.key = node.key + ':0' node_fake.value = asset.hostname queryset.append(node_fake) queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True) @@ -152,7 +131,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): class NodeAssetsApi(generics.ListAPIView): - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetSerializer def get_queryset(self): @@ -167,7 +146,7 @@ class NodeAssetsApi(generics.ListAPIView): class NodeAddChildrenApi(generics.UpdateAPIView): queryset = Node.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeAddChildrenSerializer instance = None @@ -185,7 +164,7 @@ class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddAssetsApi(generics.UpdateAPIView): serializer_class = serializers.NodeAssetsSerializer queryset = Node.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) instance = None def perform_update(self, serializer): @@ -197,7 +176,7 @@ class NodeAddAssetsApi(generics.UpdateAPIView): class NodeRemoveAssetsApi(generics.UpdateAPIView): serializer_class = serializers.NodeAssetsSerializer queryset = Node.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) instance = None def perform_update(self, serializer): @@ -213,7 +192,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView): class NodeReplaceAssetsApi(generics.UpdateAPIView): serializer_class = serializers.NodeAssetsSerializer queryset = Node.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) instance = None def perform_update(self, serializer): @@ -224,7 +203,7 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView): class RefreshNodeHardwareInfoApi(APIView): - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) model = Node def get(self, request, *args, **kwargs): @@ -237,7 +216,7 @@ class RefreshNodeHardwareInfoApi(APIView): class TestNodeConnectiveApi(APIView): - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) model = Node def get(self, request, *args, **kwargs): diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 66d62232d..f44c60f5b 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -16,8 +16,9 @@ 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 common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from ..models import SystemUser from .. import serializers from ..tasks import push_system_user_to_assets_manual, \ @@ -37,7 +38,7 @@ class SystemUserViewSet(BulkModelViewSet): """ queryset = SystemUser.objects.all() serializer_class = serializers.SystemUserSerializer - permission_classes = (IsSuperUserOrAppUser,) + permission_classes = (IsOrgAdminOrAppUser,) class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): @@ -45,7 +46,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): Get system user auth info """ queryset = SystemUser.objects.all() - permission_classes = (IsSuperUserOrAppUser,) + permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.SystemUserAuthSerializer def destroy(self, request, *args, **kwargs): @@ -59,7 +60,7 @@ class SystemUserPushApi(generics.RetrieveAPIView): Push system user to cluster assets api """ queryset = SystemUser.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) def retrieve(self, request, *args, **kwargs): system_user = self.get_object() @@ -75,7 +76,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): Push system user to cluster assets api """ queryset = SystemUser.objects.all() - permission_classes = (IsSuperUser,) + permission_classes = (IsOrgAdmin,) def retrieve(self, request, *args, **kwargs): system_user = self.get_object() diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index 5e52e3ac9..8b132b350 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -3,14 +3,17 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from ..models import Asset, AdminUser from common.utils import get_logger +from orgs.mixins import OrgModelForm + +from ..models import Asset, AdminUser + logger = get_logger(__file__) __all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm'] -class AssetCreateForm(forms.ModelForm): +class AssetCreateForm(OrgModelForm): class Meta: model = Asset fields = [ @@ -50,7 +53,7 @@ class AssetCreateForm(forms.ModelForm): } -class AssetUpdateForm(forms.ModelForm): +class AssetUpdateForm(OrgModelForm): class Meta: model = Asset fields = [ @@ -90,7 +93,7 @@ class AssetUpdateForm(forms.ModelForm): } -class AssetBulkUpdateForm(forms.ModelForm): +class AssetBulkUpdateForm(OrgModelForm): assets = forms.ModelMultipleChoiceField( required=True, help_text='* required', label=_('Select assets'), queryset=Asset.objects.all(), @@ -105,7 +108,7 @@ class AssetBulkUpdateForm(forms.ModelForm): label=_('Port'), required=False, min_value=1, max_value=65535, ) admin_user = forms.ModelChoiceField( - required=False, queryset=AdminUser.objects.all(), + required=False, queryset=AdminUser.objects, label=_("Admin user"), widget=forms.Select( attrs={ diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py index ec3af8f2e..1b005ec2f 100644 --- a/apps/assets/forms/domain.py +++ b/apps/assets/forms/domain.py @@ -3,6 +3,7 @@ from django import forms from django.utils.translation import gettext_lazy as _ +from orgs.mixins import OrgModelForm from ..models import Domain, Asset, Gateway from .user import PasswordAndKeyAuthForm @@ -34,7 +35,7 @@ class DomainForm(forms.ModelForm): return instance -class GatewayForm(PasswordAndKeyAuthForm): +class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm): def save(self, commit=True): # Because we define custom field, so we need rewrite :method: `save` diff --git a/apps/assets/hands.py b/apps/assets/hands.py index a1a376135..ffe1e35c5 100644 --- a/apps/assets/hands.py +++ b/apps/assets/hands.py @@ -11,6 +11,6 @@ """ -from common.mixins import AdminUserRequiredMixin -from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser +from common.permissions import AdminUserRequiredMixin +from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser from users.models import User, UserGroup diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index b26c50216..97bcd4b47 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -13,6 +13,7 @@ from django.core.cache import cache from ..const import ASSET_ADMIN_CONN_CACHE_KEY from .user import AdminUser, SystemUser +from orgs.mixins import OrgModelMixin,OrgManager __all__ = ['Asset'] logger = logging.getLogger(__name__) @@ -44,12 +45,7 @@ class AssetQuerySet(models.QuerySet): return self.active() -class AssetManager(models.Manager): - def get_queryset(self): - return AssetQuerySet(self.model, using=self._db) - - -class Asset(models.Model): +class Asset(OrgModelMixin): # Important PLATFORM_CHOICES = ( ('Linux', 'Linux'), @@ -71,16 +67,11 @@ class Asset(models.Model): ) 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')) - protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, - choices=PROTOCOL_CHOICES, - verbose_name=_('Protocol')) + ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) + hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) + protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) - platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, - default='Linux', verbose_name=_('Platform')) + platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform')) domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) @@ -94,11 +85,8 @@ class Asset(models.Model): null=True, verbose_name=_("Admin user")) # Some information - public_ip = models.GenericIPAddressField(max_length=32, blank=True, - null=True, - verbose_name=_('Public IP')) - number = models.CharField(max_length=32, null=True, blank=True, - verbose_name=_('Asset number')) + public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) + number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number')) # Collect vendor = models.CharField(max_length=64, null=True, blank=True, @@ -139,7 +127,7 @@ class Asset(models.Model): comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) - objects = AssetManager() + objects = OrgManager.from_queryset(AssetQuerySet)() def __str__(self): return '{0.hostname}({0.ip})'.format(self) @@ -173,6 +161,12 @@ class Asset(models.Model): nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) return nodes + @property + def org_name(self): + from orgs.models import Organization + org = Organization.get_instance(self.org_id) + return org.name + @property def hardware_info(self): if self.cpu_count: @@ -233,7 +227,7 @@ class Asset(models.Model): return data class Meta: - unique_together = ('ip', 'port') + unique_together = [('org_id', 'hostname')] verbose_name = _("Asset") @classmethod diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 908e6b647..b03010905 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -11,14 +11,15 @@ from django.conf import settings from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen from common.validators import alphanumeric +from orgs.mixins import OrgModelMixin from .utils import private_key_validator signer = get_signer() -class AssetUser(models.Model): +class AssetUser(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) + name = models.CharField(max_length=128, verbose_name=_('Name')) username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric]) _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, ]) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 61c3bcc1f..80b7ae596 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -7,12 +7,13 @@ import random from django.db import models from django.utils.translation import ugettext_lazy as _ +from orgs.mixins import OrgModelMixin from .base import AssetUser __all__ = ['Domain', 'Gateway'] -class Domain(models.Model): +class Domain(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -43,10 +44,12 @@ class Gateway(AssetUser): ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) port = models.IntegerField(default=22, verbose_name=_('Port')) protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol")) - domain = models.ForeignKey(Domain, verbose_name=_("Domain"), on_delete=models.CASCADE) + domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) def __str__(self): return self.name + class Meta: + unique_together = [('name', 'org_id')] diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index 990a71ca8..7f1d08fa1 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -4,9 +4,10 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ +from orgs.mixins import OrgModelMixin -class Label(models.Model): +class Label(OrgModelMixin): SYSTEM_CATEGORY = "S" USER_CATEGORY = "U" CATEGORY_CHOICES = ( @@ -34,4 +35,4 @@ class Label(models.Model): class Meta: db_table = "assets_label" - unique_together = ('name', 'value') + unique_together = [('name', 'value')] diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 4f4f9ad8b..8d006a40e 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -5,12 +5,15 @@ import uuid from django.db import models, transaction from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from common.utils import with_cache + +from orgs.mixins import OrgModelMixin +from orgs.utils import current_org, set_current_org, get_current_org +from orgs.models import Organization __all__ = ['Node'] -class Node(models.Model): +class Node(OrgModelMixin): 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, verbose_name=_("Value")) @@ -20,7 +23,8 @@ class Node(models.Model): is_node = True def __str__(self): - return self.full_value + return self.value + # return self.full_value def __eq__(self, other): return self.key == other.key @@ -93,12 +97,10 @@ class Node(models.Model): def get_assets(self): from .asset import Asset - if self.is_root(): - assets = Asset.objects.filter( - Q(nodes__id=self.id) | Q(nodes__isnull=True) - ) + if self.is_default_node(): + assets = Asset.objects.filter(nodes__isnull=True) else: - assets = self.assets.all() + assets = Asset.objects.filter(nodes__id=self.id) return assets def get_valid_assets(self): @@ -106,49 +108,61 @@ class Node(models.Model): def get_all_assets(self): from .asset import Asset - if self.is_root(): - assets = Asset.objects.all() + pattern = r'^{0}$|^{0}:'.format(self.key) + args = [] + kwargs = {} + if self.is_default_node(): + args.append(Q(nodes__key__regex=pattern) | Q(nodes=None)) else: - pattern = r'^{0}$|^{0}:'.format(self.key) - assets = Asset.objects.filter(nodes__key__regex=pattern) + kwargs['nodes__key__regex'] = pattern + assets = Asset.objects.filter(*args, **kwargs) return assets def get_all_valid_assets(self): return self.get_all_assets().valid() + def is_default_node(self): + return self.is_root() and self.key == '0' + def is_root(self): - return self.key == '0' + if self.key.isdigit(): + return True + else: + return False + + @property + def parent_key(self): + parent_key = ":".join(self.key.split(":")[:-1]) + return parent_key @property def parent(self): - if self.key == "0" or not self.key.startswith("0"): - return self.__class__.root() - parent_key = ":".join(self.key.split(":")[:-1]) + if self.is_root(): + return self try: - parent = self.__class__.objects.get(key=parent_key) + parent = self.__class__.objects.get(key=self.parent_key) return parent except Node.DoesNotExist: return self.__class__.root() @parent.setter def parent(self, parent): - if self.is_node: - children = self.get_all_children() - old_key = self.key - with transaction.atomic(): - self.key = parent.get_next_child_key() - for child in children: - child.key = child.key.replace(old_key, self.key, 1) - child.save() - self.save() - else: - self.key = parent.key+':fake' + if not self.is_node: + self.key = parent.key + ':fake' + return + children = self.get_all_children() + old_key = self.key + with transaction.atomic(): + self.key = parent.get_next_child_key() + for child in children: + child.key = child.key.replace(old_key, self.key, 1) + child.save() + self.save() def get_ancestor(self, with_self=False): if self.is_root(): - ancestor = self.__class__.objects.filter(key='0') - return ancestor - + root = self.__class__.root() + return [root] _key = self.key.split(':') if not with_self: _key.pop() @@ -162,10 +176,35 @@ class Node(models.Model): return ancestor @classmethod - def root(cls): - obj, created = cls.objects.get_or_create( - key='0', defaults={"key": '0', 'value': "ROOT"} - ) - print(obj) - return obj + def create_root_node(cls): + # 如果使用current_org 在set_current_org时会死循环 + _current_org = get_current_org() + with transaction.atomic(): + if _current_org.is_default(): + key = '0' + else: + set_current_org(Organization.root()) + org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$') + org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) + key = max([int(k) for k in org_nodes_roots_keys]) + 1 + set_current_org(_current_org) + root = cls.objects.create(key=key, value=_current_org.name) + return root + + @classmethod + def root(cls): + root = cls.objects.filter(key__regex=r'^[0-9]+$') + if root: + return root[0] + else: + return cls.create_root_node() + + @classmethod + def generate_fake(cls, count=100): + import random + for i in range(count): + node = random.choice(cls.objects.all()) + node.create_child('Node {}'.format(i)) + + diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 21b7c9a41..646b7204f 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -69,6 +69,7 @@ class AdminUser(AssetUser): class Meta: ordering = ['name'] + unique_together = [('name', 'org_id')] verbose_name = _("Admin user") @classmethod @@ -176,6 +177,7 @@ class SystemUser(AssetUser): class Meta: ordering = ['name'] + unique_together = [('name', 'org_id')] verbose_name = _("System user") @classmethod diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index dbd0d1b39..e1ecdf1c3 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer): 管理用户更新关联到的集群 """ nodes = serializers.PrimaryKeyRelatedField( - many=True, queryset=Node.objects.all() + many=True, queryset = Node.objects.all() ) class Meta: diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index e63735794..36639a17e 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -20,12 +20,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): model = Asset list_serializer_class = BulkListSerializer fields = '__all__' - validators = [] # If not set to [], partial bulk update will be error + # 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', + 'hardware_info', 'is_connective', 'org_name' ]) return fields @@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): fields = ( "id", "hostname", "ip", "port", "system_users_granted", "is_active", "system_users_join", "os", 'domain', - "platform", "comment", "protocol", + "platform", "comment", "protocol", "org_id", "org_name", ) @staticmethod @@ -61,6 +61,6 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer): model = Asset fields = ( "id", "hostname", "system_users_granted", - "is_active", "system_users_join", - "os", "platform", "comment", + "is_active", "system_users_join", "org_name", + "os", "platform", "comment", "org_id", "protocol" ) diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index 56e01f742..4f7031065 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -26,7 +26,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): model = Node fields = [ 'id', 'key', 'name', 'value', 'parent', - 'assets_granted', 'assets_amount', + 'assets_granted', 'assets_amount', 'org_id', ] @staticmethod @@ -43,12 +43,16 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): class NodeSerializer(serializers.ModelSerializer): - parent = serializers.SerializerMethodField() assets_amount = serializers.SerializerMethodField() + tree_id = serializers.SerializerMethodField() + tree_parent = serializers.SerializerMethodField() class Meta: model = Node - fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node'] + fields = [ + 'id', 'key', 'value', 'assets_amount', + 'is_node', 'org_id', 'tree_id', 'tree_parent', + ] list_serializer_class = BulkListSerializer def validate(self, data): @@ -63,12 +67,16 @@ class NodeSerializer(serializers.ModelSerializer): return data @staticmethod - def get_parent(obj): - return obj.parent.id if obj.is_node else obj.parent_id + def get_assets_amount(obj): + return obj.assets__count if hasattr(obj, 'assets__count') else 0 @staticmethod - def get_assets_amount(obj): - return obj.get_all_assets().count() if obj.is_node else 0 + def get_tree_id(obj): + return obj.key + + @staticmethod + def get_tree_parent(obj): + return obj.parent_key def get_fields(self): fields = super().get_fields() @@ -78,7 +86,7 @@ class NodeSerializer(serializers.ModelSerializer): class NodeAssetsSerializer(serializers.ModelSerializer): - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) + assets = serializers.PrimaryKeyRelatedField(many=True, queryset = Asset.objects.all()) class Meta: model = Node diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html index faf569137..ea8d59e49 100644 --- a/apps/assets/templates/assets/_asset_list_modal.html +++ b/apps/assets/templates/assets/_asset_list_modal.html @@ -71,7 +71,7 @@ function initTable2() { function onSelected2(event, treeNode) { var url = asset_table2.ajax.url(); - url = setUrlParam(url, "node_id", treeNode.id); + url = setUrlParam(url, "node_id", treeNode.node_id); setCookie('node_selected', treeNode.id); asset_table2.ajax.url(url); asset_table2.ajax.reload(); @@ -97,17 +97,20 @@ function initTree2() { var zNodes = []; $.get("{% url 'api-assets:node-list' %}", function(data, status){ $.each(data, function (index, value) { - value["pId"] = value["parent"]; + value["node_id"] = value["id"]; + value["id"] = value["tree_id"]; + value["pId"] = value["tree_parent"]; {#value["open"] = true;#} if (value["key"] === "0") { value["open"] = true; } value["name"] = value["value"] + ' (' + value['assets_amount'] + ')'; - value['value'] = value['value']; }); zNodes = data; $.fn.zTree.init($("#assetTree2"), setting, zNodes); zTree2 = $.fn.zTree.getZTreeObj("assetTree2"); + var root = zTree2.getNodes()[0]; + zTree2.expandNode(root); }); } diff --git a/apps/assets/templates/assets/_user_asset_detail_modal.html b/apps/assets/templates/assets/_user_asset_detail_modal.html new file mode 100644 index 000000000..ca2b8f252 --- /dev/null +++ b/apps/assets/templates/assets/_user_asset_detail_modal.html @@ -0,0 +1,24 @@ +{% extends '_modal.html' %} +{% load i18n %} +{% load static %} + +{% block modal_id %}user_asset_detail_modal{% endblock %} + +{% block modal_title %}{% trans "Asset detail" %}{% endblock %} + +{% block modal_body %} +
+ + + +
+
+{% endblock %} + +{% block modal_button %} + +{% endblock %} diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index b07a7c348..2f36688be 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -130,7 +130,7 @@ - {% if user.is_superuser %} + {% if user.is_superuser or user.is_org_admin %}
diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index b5e53aaba..4986bb959 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -10,6 +10,7 @@ {% block custom_head_css_js %} +{# #}