From 1eca517978828d8e7ca70bd79e502d297702d6d2 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>
Date: Mon, 20 May 2019 19:39:53 +0800
Subject: [PATCH] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0=E5=8A=9F?=
=?UTF-8?q?=E8=83=BD=20RemoteApp=20(#2706)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* [Feature] RemoteApp添加Model
* [Feature] RemoteApp添加ViewSet API
* [Feature] RemoteApp添加获取connection-info API
* [Feature] Perms模块修改目录结构
* [Feature] RemoteAppPermission添加Model
* [Feature] RemoteAppPermission添加ViewSet API
* [Feature] RemoteAppPermission添加用户/用户组获取被授权的RemoteApp API
* [Feature] RemoteAppPermission添加校验用户对RemoteApp的权限 API
* [Feature] RemoteAppPermission添加获取用户授权的RemoteApp树 API
* [Feature] RemoteAppPermission添加<添加/移除>所授权的<用户/RemoteApp> API
* [Feature] RemoteApp添加创建、更新、详情、删除、用户RemoteApp等页面
* [Feature] RemoteAppPermission添加创建、更新、详情、删除、授权用户、授权RemoteApp等页面
* [Feature] RemoteApp从assets模块迁移到新添加的applications模块
* [Feature] RemoteApp/RemoteAppPermission添加迁移文件
* [Feature] RemoteApp/RemoteAppPermission修改小细节
* [Feature] RemoteApp/RemoteAppPermission修改小细节2
* [Feature] RemoteApp/RemoteAppPermission修改小细节3
* [Feature] RemoteApp更新迁移文件
* [Feature] RemoteApp/RemoteAppPermission添加翻译信息
* [Feature] RemoteApp/RemoteAppPermission删除迁移文件
* [Feature] RemoteApp/RemoteAppPermission添加迁移文件
* [Feature] RemoteApp/RemoteAppPermission修改代码风格
---
apps/applications/__init__.py | 0
apps/applications/admin.py | 3 +
apps/applications/api/__init__.py | 1 +
apps/applications/api/remote_app.py | 31 +
apps/applications/apps.py | 7 +
apps/applications/const.py | 68 +
apps/applications/forms/__init__.py | 1 +
apps/applications/forms/remote_app.py | 111 ++
apps/applications/hands.py | 16 +
apps/applications/migrations/0001_initial.py | 42 +
apps/applications/migrations/__init__.py | 0
apps/applications/models/__init__.py | 1 +
apps/applications/models/remote_app.py | 89 ++
apps/applications/serializers/__init__.py | 1 +
apps/applications/serializers/remote_app.py | 103 ++
.../remote_app_create_update.html | 91 ++
.../applications/remote_app_detail.html | 109 ++
.../applications/remote_app_list.html | 90 ++
.../applications/user_remote_app_list.html | 79 ++
apps/applications/tests.py | 3 +
apps/applications/urls/__init__.py | 7 +
apps/applications/urls/api_urls.py | 20 +
apps/applications/urls/views_urls.py | 16 +
apps/applications/views/__init__.py | 1 +
apps/applications/views/remote_app.py | 99 ++
apps/assets/const.py | 4 +
apps/assets/models/__init__.py | 1 +
apps/common/fields/model.py | 5 +-
apps/jumpserver/settings.py | 1 +
apps/jumpserver/urls.py | 2 +
apps/locale/zh/LC_MESSAGES/django.mo | Bin 71684 -> 74215 bytes
apps/locale/zh/LC_MESSAGES/django.po | 1211 ++++++++++-------
apps/perms/api/__init__.py | 3 +-
.../{permission.py => asset_permission.py} | 0
apps/perms/api/remote_app_permission.py | 101 ++
apps/perms/api/user_group_permission.py | 24 +-
apps/perms/api/user_permission.py | 86 +-
apps/perms/forms/__init__.py | 5 +
.../{forms.py => forms/asset_permission.py} | 6 +-
apps/perms/forms/remote_app_permission.py | 49 +
apps/perms/hands.py | 7 +-
.../migrations/0005_auto_20190520_1904.py | 45 +
apps/perms/mixins.py | 40 +
apps/perms/models/__init__.py | 5 +
.../{models.py => models/asset_permission.py} | 7 +-
apps/perms/models/remote_app_permission.py | 75 +
apps/perms/serializers/__init__.py | 5 +
.../asset_permission.py} | 4 +-
.../serializers/remote_app_permission.py | 36 +
.../remote_app_permission_create_update.html | 120 ++
.../perms/remote_app_permission_detail.html | 169 +++
.../perms/remote_app_permission_list.html | 93 ++
.../remote_app_permission_remote_app.html | 164 +++
.../perms/remote_app_permission_user.html | 256 ++++
apps/perms/urls/api_urls.py | 47 +-
apps/perms/urls/views_urls.py | 9 +
apps/perms/utils/__init__.py | 5 +
.../{utils.py => utils/asset_permission.py} | 18 +-
apps/perms/utils/remote_app_permission.py | 81 ++
apps/perms/views/__init__.py | 5 +
.../{views.py => views/asset_permission.py} | 19 +-
apps/perms/views/remote_app_permission.py | 144 ++
apps/templates/_nav.html | 11 +
apps/templates/_nav_user.html | 12 +
64 files changed, 3355 insertions(+), 509 deletions(-)
create mode 100644 apps/applications/__init__.py
create mode 100644 apps/applications/admin.py
create mode 100644 apps/applications/api/__init__.py
create mode 100644 apps/applications/api/remote_app.py
create mode 100644 apps/applications/apps.py
create mode 100644 apps/applications/const.py
create mode 100644 apps/applications/forms/__init__.py
create mode 100644 apps/applications/forms/remote_app.py
create mode 100644 apps/applications/hands.py
create mode 100644 apps/applications/migrations/0001_initial.py
create mode 100644 apps/applications/migrations/__init__.py
create mode 100644 apps/applications/models/__init__.py
create mode 100644 apps/applications/models/remote_app.py
create mode 100644 apps/applications/serializers/__init__.py
create mode 100644 apps/applications/serializers/remote_app.py
create mode 100644 apps/applications/templates/applications/remote_app_create_update.html
create mode 100644 apps/applications/templates/applications/remote_app_detail.html
create mode 100644 apps/applications/templates/applications/remote_app_list.html
create mode 100644 apps/applications/templates/applications/user_remote_app_list.html
create mode 100644 apps/applications/tests.py
create mode 100644 apps/applications/urls/__init__.py
create mode 100644 apps/applications/urls/api_urls.py
create mode 100644 apps/applications/urls/views_urls.py
create mode 100644 apps/applications/views/__init__.py
create mode 100644 apps/applications/views/remote_app.py
rename apps/perms/api/{permission.py => asset_permission.py} (100%)
create mode 100644 apps/perms/api/remote_app_permission.py
create mode 100644 apps/perms/forms/__init__.py
rename apps/perms/{forms.py => forms/asset_permission.py} (97%)
create mode 100644 apps/perms/forms/remote_app_permission.py
create mode 100644 apps/perms/migrations/0005_auto_20190520_1904.py
create mode 100644 apps/perms/models/__init__.py
rename apps/perms/{models.py => models/asset_permission.py} (97%)
create mode 100644 apps/perms/models/remote_app_permission.py
create mode 100644 apps/perms/serializers/__init__.py
rename apps/perms/{serializers.py => serializers/asset_permission.py} (97%)
create mode 100644 apps/perms/serializers/remote_app_permission.py
create mode 100644 apps/perms/templates/perms/remote_app_permission_create_update.html
create mode 100644 apps/perms/templates/perms/remote_app_permission_detail.html
create mode 100644 apps/perms/templates/perms/remote_app_permission_list.html
create mode 100644 apps/perms/templates/perms/remote_app_permission_remote_app.html
create mode 100644 apps/perms/templates/perms/remote_app_permission_user.html
create mode 100644 apps/perms/utils/__init__.py
rename apps/perms/{utils.py => utils/asset_permission.py} (98%)
create mode 100644 apps/perms/utils/remote_app_permission.py
create mode 100644 apps/perms/views/__init__.py
rename apps/perms/{views.py => views/asset_permission.py} (91%)
create mode 100644 apps/perms/views/remote_app_permission.py
diff --git a/apps/applications/__init__.py b/apps/applications/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/applications/admin.py b/apps/applications/admin.py
new file mode 100644
index 000000000..8c38f3f3d
--- /dev/null
+++ b/apps/applications/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/apps/applications/api/__init__.py b/apps/applications/api/__init__.py
new file mode 100644
index 000000000..e6bc7adb4
--- /dev/null
+++ b/apps/applications/api/__init__.py
@@ -0,0 +1 @@
+from .remote_app import *
diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py
new file mode 100644
index 000000000..c41c5d100
--- /dev/null
+++ b/apps/applications/api/remote_app.py
@@ -0,0 +1,31 @@
+# coding: utf-8
+#
+
+
+from rest_framework import generics
+from rest_framework.pagination import LimitOffsetPagination
+from rest_framework_bulk import BulkModelViewSet
+
+from ..hands import IsOrgAdmin, IsAppUser
+from ..models import RemoteApp
+from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
+
+
+__all__ = [
+ 'RemoteAppViewSet', 'RemoteAppConnectionInfoApi',
+]
+
+
+class RemoteAppViewSet(BulkModelViewSet):
+ filter_fields = ('name',)
+ search_fields = filter_fields
+ permission_classes = (IsOrgAdmin,)
+ queryset = RemoteApp.objects.all()
+ serializer_class = RemoteAppSerializer
+ pagination_class = LimitOffsetPagination
+
+
+class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
+ queryset = RemoteApp.objects.all()
+ permission_classes = (IsAppUser, )
+ serializer_class = RemoteAppConnectionInfoSerializer
diff --git a/apps/applications/apps.py b/apps/applications/apps.py
new file mode 100644
index 000000000..3c22ddedc
--- /dev/null
+++ b/apps/applications/apps.py
@@ -0,0 +1,7 @@
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class ApplicationsConfig(AppConfig):
+ name = 'applications'
diff --git a/apps/applications/const.py b/apps/applications/const.py
new file mode 100644
index 000000000..b64b1a14b
--- /dev/null
+++ b/apps/applications/const.py
@@ -0,0 +1,68 @@
+# coding: utf-8
+#
+
+from django.utils.translation import ugettext_lazy as _
+
+
+# RemoteApp
+REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor'
+
+REMOTE_APP_TYPE_CHROME = 'chrome'
+REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench'
+REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client'
+REMOTE_APP_TYPE_CUSTOM = 'custom'
+
+REMOTE_APP_TYPE_CHOICES = (
+ (
+ _('Browser'),
+ (
+ (REMOTE_APP_TYPE_CHROME, 'Chrome'),
+ )
+ ),
+ (
+ _('Database tools'),
+ (
+ (REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
+ )
+ ),
+ (
+ _('Virtualization tools'),
+ (
+ (REMOTE_APP_TYPE_VMWARE_CLIENT, 'VMware Client'),
+ )
+ ),
+ (REMOTE_APP_TYPE_CUSTOM, _('Custom')),
+
+)
+
+# Fields attribute write_only default => False
+
+REMOTE_APP_TYPE_CHROME_FIELDS = [
+ {'name': 'chrome_target'},
+ {'name': 'chrome_username'},
+ {'name': 'chrome_password', 'write_only': True}
+]
+REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [
+ {'name': 'mysql_workbench_ip'},
+ {'name': 'mysql_workbench_name'},
+ {'name': 'mysql_workbench_username'},
+ {'name': 'mysql_workbench_password', 'write_only': True}
+]
+REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [
+ {'name': 'vmware_target'},
+ {'name': 'vmware_username'},
+ {'name': 'vmware_password', 'write_only': True}
+]
+REMOTE_APP_TYPE_CUSTOM_FIELDS = [
+ {'name': 'custom_cmdline'},
+ {'name': 'custom_target'},
+ {'name': 'custom_username'},
+ {'name': 'custom_password', 'write_only': True}
+]
+
+REMOTE_APP_TYPE_MAP_FIELDS = {
+ REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS,
+ REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS,
+ REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS,
+ REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS
+}
diff --git a/apps/applications/forms/__init__.py b/apps/applications/forms/__init__.py
new file mode 100644
index 000000000..e6bc7adb4
--- /dev/null
+++ b/apps/applications/forms/__init__.py
@@ -0,0 +1 @@
+from .remote_app import *
diff --git a/apps/applications/forms/remote_app.py b/apps/applications/forms/remote_app.py
new file mode 100644
index 000000000..88f1912e0
--- /dev/null
+++ b/apps/applications/forms/remote_app.py
@@ -0,0 +1,111 @@
+# coding: utf-8
+#
+
+from django.utils.translation import ugettext as _
+from django import forms
+
+from orgs.mixins import OrgModelForm
+from assets.models import Asset, SystemUser
+
+from ..models import RemoteApp
+
+
+__all__ = [
+ 'RemoteAppCreateUpdateForm',
+]
+
+
+class RemoteAppTypeChromeForm(forms.ModelForm):
+ chrome_target = forms.CharField(
+ max_length=128, label=_('Target URL'), required=False
+ )
+ chrome_username = forms.CharField(
+ max_length=128, label=_('Login username'), required=False
+ )
+ chrome_password = forms.CharField(
+ widget=forms.PasswordInput, strip=True,
+ max_length=128, label=_('Login password'), required=False
+ )
+
+
+class RemoteAppTypeMySQLWorkbenchForm(forms.ModelForm):
+ mysql_workbench_ip = forms.CharField(
+ max_length=128, label=_('Database IP'), required=False
+ )
+ mysql_workbench_name = forms.CharField(
+ max_length=128, label=_('Database name'), required=False
+ )
+ mysql_workbench_username = forms.CharField(
+ max_length=128, label=_('Database username'), required=False
+ )
+ mysql_workbench_password = forms.CharField(
+ widget=forms.PasswordInput, strip=True,
+ max_length=128, label=_('Database password'), required=False
+ )
+
+
+class RemoteAppTypeVMwareForm(forms.ModelForm):
+ vmware_target = forms.CharField(
+ max_length=128, label=_('Target address'), required=False
+ )
+ vmware_username = forms.CharField(
+ max_length=128, label=_('Login username'), required=False
+ )
+ vmware_password = forms.CharField(
+ widget=forms.PasswordInput, strip=True,
+ max_length=128, label=_('Login password'), required=False
+ )
+
+
+class RemoteAppTypeCustomForm(forms.ModelForm):
+ custom_cmdline = forms.CharField(
+ max_length=128, label=_('Operating parameter'), required=False
+ )
+ custom_target = forms.CharField(
+ max_length=128, label=_('Target address'), required=False
+ )
+ custom_username = forms.CharField(
+ max_length=128, label=_('Login username'), required=False
+ )
+ custom_password = forms.CharField(
+ widget=forms.PasswordInput, strip=True,
+ max_length=128, label=_('Login password'), required=False
+ )
+
+
+class RemoteAppTypeForms(
+ RemoteAppTypeChromeForm,
+ RemoteAppTypeMySQLWorkbenchForm,
+ RemoteAppTypeVMwareForm,
+ RemoteAppTypeCustomForm
+):
+ pass
+
+
+class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
+ def __init__(self, *args, **kwargs):
+ # 过滤RDP资产和系统用户
+ super().__init__(*args, **kwargs)
+ field_asset = self.fields['asset']
+ field_asset.queryset = field_asset.queryset.filter(
+ protocol=Asset.PROTOCOL_RDP
+ )
+ field_system_user = self.fields['system_user']
+ field_system_user.queryset = field_system_user.queryset.filter(
+ protocol=SystemUser.PROTOCOL_RDP
+ )
+
+ class Meta:
+ model = RemoteApp
+ fields = [
+ 'name', 'asset', 'system_user', 'type', 'path', 'comment'
+ ]
+ widgets = {
+ 'asset': forms.Select(attrs={
+ 'class': 'select2', 'data-placeholder': _('Asset')
+ }),
+ 'system_user': forms.Select(attrs={
+ 'class': 'select2', 'data-placeholder': _('System user')
+ })
+ }
+
diff --git a/apps/applications/hands.py b/apps/applications/hands.py
new file mode 100644
index 000000000..ffe1e35c5
--- /dev/null
+++ b/apps/applications/hands.py
@@ -0,0 +1,16 @@
+"""
+ jumpserver.__app__.hands.py
+ ~~~~~~~~~~~~~~~~~
+
+ This app depends other apps api, function .. should be import or write mack here.
+
+ Other module of this app shouldn't connect with other app.
+
+ :copyright: (c) 2014-2018 by Jumpserver Team.
+ :license: GPL v2, see LICENSE for more details.
+"""
+
+
+from common.permissions import AdminUserRequiredMixin
+from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
+from users.models import User, UserGroup
diff --git a/apps/applications/migrations/0001_initial.py b/apps/applications/migrations/0001_initial.py
new file mode 100644
index 000000000..35d6dc103
--- /dev/null
+++ b/apps/applications/migrations/0001_initial.py
@@ -0,0 +1,42 @@
+# Generated by Django 2.1.7 on 2019-05-20 11:04
+
+import common.fields.model
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('assets', '0026_auto_20190325_2035'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RemoteApp',
+ fields=[
+ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
+ ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
+ ('name', models.CharField(max_length=128, verbose_name='Name')),
+ ('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'VMware Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')),
+ ('path', models.CharField(max_length=128, verbose_name='App path')),
+ ('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')),
+ ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
+ ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
+ ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
+ ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
+ ('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')),
+ ],
+ options={
+ 'verbose_name': 'RemoteApp',
+ 'ordering': ('name',),
+ },
+ ),
+ migrations.AlterUniqueTogether(
+ name='remoteapp',
+ unique_together={('org_id', 'name')},
+ ),
+ ]
diff --git a/apps/applications/migrations/__init__.py b/apps/applications/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/applications/models/__init__.py b/apps/applications/models/__init__.py
new file mode 100644
index 000000000..e6bc7adb4
--- /dev/null
+++ b/apps/applications/models/__init__.py
@@ -0,0 +1 @@
+from .remote_app import *
diff --git a/apps/applications/models/remote_app.py b/apps/applications/models/remote_app.py
new file mode 100644
index 000000000..772d39834
--- /dev/null
+++ b/apps/applications/models/remote_app.py
@@ -0,0 +1,89 @@
+# coding: utf-8
+#
+
+import uuid
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from orgs.mixins import OrgModelMixin
+from common.fields.model import EncryptJsonDictTextField
+
+from .. import const
+
+
+__all__ = [
+ 'RemoteApp',
+]
+
+
+class RemoteApp(OrgModelMixin):
+ id = models.UUIDField(default=uuid.uuid4, primary_key=True)
+ name = models.CharField(max_length=128, verbose_name=_('Name'))
+ asset = models.ForeignKey(
+ 'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
+ )
+ system_user = models.ForeignKey(
+ 'assets.SystemUser', on_delete=models.CASCADE,
+ verbose_name=_('System user')
+ )
+ type = models.CharField(
+ default=const.REMOTE_APP_TYPE_CHROME,
+ choices=const.REMOTE_APP_TYPE_CHOICES,
+ max_length=128, verbose_name=_('App type')
+ )
+ path = models.CharField(
+ max_length=128, blank=False, null=False,
+ verbose_name=_('App path')
+ )
+ params = EncryptJsonDictTextField(
+ max_length=4096, default={}, blank=True, null=True,
+ verbose_name=_('Parameters')
+ )
+ 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')
+ )
+
+ class Meta:
+ verbose_name = _("RemoteApp")
+ unique_together = [('org_id', 'name')]
+ ordering = ('name', )
+
+ def __str__(self):
+ return self.name
+
+ @property
+ def parameters(self):
+ """
+ 返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
+ """
+ _parameters = list()
+ _parameters.append(self.type)
+ path = '\"%s\"' % self.path
+ _parameters.append(path)
+ for field in const.REMOTE_APP_TYPE_MAP_FIELDS[self.type]:
+ value = self.params.get(field['name'])
+ if value is None:
+ continue
+ _parameters.append(value)
+ _parameters = ' '.join(_parameters)
+ return _parameters
+
+ @property
+ def asset_info(self):
+ return {
+ 'id': self.asset.id,
+ 'hostname': self.asset.hostname
+ }
+
+ @property
+ def system_user_info(self):
+ return {
+ 'id': self.system_user.id,
+ 'name': self.system_user.name
+ }
diff --git a/apps/applications/serializers/__init__.py b/apps/applications/serializers/__init__.py
new file mode 100644
index 000000000..e6bc7adb4
--- /dev/null
+++ b/apps/applications/serializers/__init__.py
@@ -0,0 +1 @@
+from .remote_app import *
diff --git a/apps/applications/serializers/remote_app.py b/apps/applications/serializers/remote_app.py
new file mode 100644
index 000000000..6957d11d5
--- /dev/null
+++ b/apps/applications/serializers/remote_app.py
@@ -0,0 +1,103 @@
+# coding: utf-8
+#
+
+
+from rest_framework import serializers
+
+from common.mixins import BulkSerializerMixin
+from common.serializers import AdaptedBulkListSerializer
+
+from .. import const
+from ..models import RemoteApp
+
+
+__all__ = [
+ 'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
+]
+
+
+class RemoteAppParamsDictField(serializers.DictField):
+ """
+ RemoteApp field => params
+ """
+ @staticmethod
+ def filter_attribute(attribute, instance):
+ """
+ 过滤掉params字段值中write_only特性的key-value值
+ For example, the chrome_password field is not returned when serializing
+ {
+ 'chrome_target': 'http://www.jumpserver.org/',
+ 'chrome_username': 'admin',
+ 'chrome_password': 'admin',
+ }
+ """
+ for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]:
+ if field.get('write_only', False):
+ attribute.pop(field['name'], None)
+ return attribute
+
+ def get_attribute(self, instance):
+ """
+ 序列化时调用
+ """
+ attribute = super().get_attribute(instance)
+ attribute = self.filter_attribute(attribute, instance)
+ return attribute
+
+ @staticmethod
+ def filter_value(dictionary, value):
+ """
+ 过滤掉不属于当前app_type所包含的key-value值
+ """
+ app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME)
+ fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type]
+ fields_names = [field['name'] for field in fields]
+ no_need_keys = [k for k in value.keys() if k not in fields_names]
+ for k in no_need_keys:
+ value.pop(k)
+ return value
+
+ def get_value(self, dictionary):
+ """
+ 反序列化时调用
+ """
+ value = super().get_value(dictionary)
+ value = self.filter_value(dictionary, value)
+ return value
+
+
+class RemoteAppSerializer(BulkSerializerMixin, serializers.ModelSerializer):
+ params = RemoteAppParamsDictField()
+
+ class Meta:
+ model = RemoteApp
+ list_serializer_class = AdaptedBulkListSerializer
+ fields = [
+ 'id', 'name', 'asset', 'system_user', 'type', 'path', 'params',
+ 'comment', 'created_by', 'date_created', 'asset_info',
+ 'system_user_info', 'get_type_display',
+ ]
+ read_only_fields = [
+ 'created_by', 'date_created', 'asset_info',
+ 'system_user_info', 'get_type_display'
+ ]
+
+
+class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
+ parameter_remote_app = serializers.SerializerMethodField()
+
+ class Meta:
+ model = RemoteApp
+ fields = [
+ 'id', 'name', 'asset', 'system_user', 'parameter_remote_app',
+ ]
+ read_only_fields = ['parameter_remote_app']
+
+ @staticmethod
+ def get_parameter_remote_app(obj):
+ parameter = {
+ 'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
+ 'working_directory': '',
+ 'parameters': obj.parameters,
+ }
+ return parameter
diff --git a/apps/applications/templates/applications/remote_app_create_update.html b/apps/applications/templates/applications/remote_app_create_update.html
new file mode 100644
index 000000000..e64b8994c
--- /dev/null
+++ b/apps/applications/templates/applications/remote_app_create_update.html
@@ -0,0 +1,91 @@
+{% extends '_base_create_update.html' %}
+{% load static %}
+{% load bootstrap3 %}
+{% load i18n %}
+
+{% block form %}
+
+{% endblock %}
+
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/applications/templates/applications/remote_app_detail.html b/apps/applications/templates/applications/remote_app_detail.html
new file mode 100644
index 000000000..d006bb51a
--- /dev/null
+++ b/apps/applications/templates/applications/remote_app_detail.html
@@ -0,0 +1,109 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block custom_head_css_js %}
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
{{ remote_app.name }}
+
+
+
+
+
+
+ {% trans 'Name' %}:
+ {{ remote_app.name }}
+
+
+ {% trans 'Asset' %}:
+ {{ remote_app.asset.hostname }}
+
+
+ {% trans 'System user' %}:
+ {{ remote_app.system_user.name }}
+
+
+ {% trans 'App type' %}:
+ {{ remote_app.get_type_display }}
+
+
+ {% trans 'App path' %}:
+ {{ remote_app.path }}
+
+
+ {% trans 'Date created' %}:
+ {{ remote_app.date_created }}
+
+
+ {% trans 'Created by' %}:
+ {{ remote_app.created_by }}
+
+
+ {% trans 'Comment' %}:
+ {{ remote_app.comment }}
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/applications/templates/applications/remote_app_list.html b/apps/applications/templates/applications/remote_app_list.html
new file mode 100644
index 000000000..c2f64235e
--- /dev/null
+++ b/apps/applications/templates/applications/remote_app_list.html
@@ -0,0 +1,90 @@
+{% extends '_base_list.html' %}
+{% load i18n static %}
+{% block help_message %}
+
+ {% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %}
+
{% trans 'Download application loader' %}
+
+{% endblock %}
+{% block table_search %}{% endblock %}
+{% block table_container %}
+
+
+{% endblock %}
+{% block content_bottom_left %}{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/applications/templates/applications/user_remote_app_list.html b/apps/applications/templates/applications/user_remote_app_list.html
new file mode 100644
index 000000000..4199e805f
--- /dev/null
+++ b/apps/applications/templates/applications/user_remote_app_list.html
@@ -0,0 +1,79 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block custom_head_css_js %}
+
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
diff --git a/apps/applications/tests.py b/apps/applications/tests.py
new file mode 100644
index 000000000..7ce503c2d
--- /dev/null
+++ b/apps/applications/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/apps/applications/urls/__init__.py b/apps/applications/urls/__init__.py
new file mode 100644
index 000000000..3aab4972f
--- /dev/null
+++ b/apps/applications/urls/__init__.py
@@ -0,0 +1,7 @@
+# coding: utf-8
+#
+
+
+__all__ = [
+
+]
diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py
new file mode 100644
index 000000000..97487b5a1
--- /dev/null
+++ b/apps/applications/urls/api_urls.py
@@ -0,0 +1,20 @@
+# coding:utf-8
+#
+
+from django.urls import path
+from rest_framework_bulk.routes import BulkRouter
+
+from .. import api
+
+app_name = 'applications'
+
+router = BulkRouter()
+router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app')
+
+urlpatterns = [
+ path('remote-apps//connection-info/',
+ api.RemoteAppConnectionInfoApi.as_view(),
+ name='remote-app-connection-info')
+]
+
+urlpatterns += router.urls
diff --git a/apps/applications/urls/views_urls.py b/apps/applications/urls/views_urls.py
new file mode 100644
index 000000000..3ffcffc5c
--- /dev/null
+++ b/apps/applications/urls/views_urls.py
@@ -0,0 +1,16 @@
+# coding:utf-8
+from django.urls import path
+from .. import views
+
+app_name = 'applications'
+
+urlpatterns = [
+ # RemoteApp
+ path('remote-app/', views.RemoteAppListView.as_view(), name='remote-app-list'),
+ path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'),
+ path('remote-app//update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'),
+ path('remote-app//', views.RemoteAppDetailView.as_view(), name='remote-app-detail'),
+ # User RemoteApp view
+ path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list')
+
+]
diff --git a/apps/applications/views/__init__.py b/apps/applications/views/__init__.py
new file mode 100644
index 000000000..e6bc7adb4
--- /dev/null
+++ b/apps/applications/views/__init__.py
@@ -0,0 +1 @@
+from .remote_app import *
diff --git a/apps/applications/views/remote_app.py b/apps/applications/views/remote_app.py
new file mode 100644
index 000000000..e21db3ad8
--- /dev/null
+++ b/apps/applications/views/remote_app.py
@@ -0,0 +1,99 @@
+# coding: utf-8
+#
+
+from django.utils.translation import ugettext as _
+from django.views.generic import TemplateView
+from django.views.generic.edit import CreateView, UpdateView
+from django.views.generic.detail import DetailView
+from django.contrib.messages.views import SuccessMessageMixin
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.urls import reverse_lazy
+
+
+from common.permissions import AdminUserRequiredMixin
+from common.const import create_success_msg, update_success_msg
+
+from ..models import RemoteApp
+from .. import forms
+
+
+__all__ = [
+ 'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView',
+ 'RemoteAppDetailView', 'UserRemoteAppListView',
+]
+
+
+class RemoteAppListView(AdminUserRequiredMixin, TemplateView):
+ template_name = 'applications/remote_app_list.html'
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Assets'),
+ 'action': _('RemoteApp list'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+
+class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
+ template_name = 'applications/remote_app_create_update.html'
+ model = RemoteApp
+ form_class = forms.RemoteAppCreateUpdateForm
+ success_url = reverse_lazy('applications:remote-app-list')
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Assets'),
+ 'action': _('Create RemoteApp'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+ def get_success_message(self, cleaned_data):
+ return create_success_msg % ({'name': cleaned_data['name']})
+
+
+class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
+ template_name = 'applications/remote_app_create_update.html'
+ model = RemoteApp
+ form_class = forms.RemoteAppCreateUpdateForm
+ success_url = reverse_lazy('applications:remote-app-list')
+
+ def get_initial(self):
+ return {k: v for k, v in self.object.params.items()}
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Assets'),
+ 'action': _('Update RemoteApp'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+ def get_success_message(self, cleaned_data):
+ return update_success_msg % ({'name': cleaned_data['name']})
+
+
+class RemoteAppDetailView(AdminUserRequiredMixin, DetailView):
+ template_name = 'applications/remote_app_detail.html'
+ model = RemoteApp
+ context_object_name = 'remote_app'
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Assets'),
+ 'action': _('RemoteApp detail'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+
+class UserRemoteAppListView(LoginRequiredMixin, TemplateView):
+ template_name = 'applications/user_remote_app_list.html'
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'action': _('My RemoteApp'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
diff --git a/apps/assets/const.py b/apps/assets/const.py
index a110683d0..0cd74ba0d 100644
--- a/apps/assets/const.py
+++ b/apps/assets/const.py
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
#
+from django.utils.translation import ugettext_lazy as _
+
+
UPDATE_ASSETS_HARDWARE_TASKS = [
{
'name': "setup",
@@ -51,3 +54,4 @@ TASK_OPTIONS = {
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
+
diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py
index b87a18796..c82c66a69 100644
--- a/apps/assets/models/__init__.py
+++ b/apps/assets/models/__init__.py
@@ -8,3 +8,4 @@ from .asset import *
from .cmd_filter import *
from .utils import *
from .authbook import *
+from applications.models.remote_app import *
diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py
index b844d3517..c2bb1e0be 100644
--- a/apps/common/fields/model.py
+++ b/apps/common/fields/model.py
@@ -11,7 +11,7 @@ __all__ = [
'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin',
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
- 'EncryptTextField', 'EncryptMixin',
+ 'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
]
signer = get_signer()
@@ -129,4 +129,7 @@ class EncryptCharField(EncryptMixin, models.CharField):
super().__init__(*args, **kwargs)
+class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
+ pass
+
diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py
index d97acb361..a6ec9c92a 100644
--- a/apps/jumpserver/settings.py
+++ b/apps/jumpserver/settings.py
@@ -67,6 +67,7 @@ INSTALLED_APPS = [
'terminal.apps.TerminalConfig',
'audits.apps.AuditsConfig',
'authentication.apps.AuthenticationConfig', # authentication
+ 'applications.apps.ApplicationsConfig',
'rest_framework',
'rest_framework_swagger',
'drf_yasg',
diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py
index 5ef225828..a2538b6c0 100644
--- a/apps/jumpserver/urls.py
+++ b/apps/jumpserver/urls.py
@@ -20,6 +20,7 @@ api_v1 = [
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
+ path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')),
]
api_v2 = [
@@ -37,6 +38,7 @@ app_view_patterns = [
path('audits/', include('audits.urls.view_urls', namespace='audits')),
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
path('auth/', include('authentication.urls.view_urls'), name='auth'),
+ path('applications/', include('applications.urls.views_urls', namespace='applications')),
]
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index 4d188c93e46ee615c0be0c00ea39a0646ea361be..0d17f388ba3f785308213fd499c61003499de620 100644
GIT binary patch
delta 23608
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
delta 21334
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\n"
"Language-Team: Jumpserver team\n"
@@ -17,6 +17,573 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+#: applications/const.py:17
+msgid "Browser"
+msgstr "浏览器"
+
+#: applications/const.py:23
+msgid "Database tools"
+msgstr "数据库工具"
+
+#: applications/const.py:29
+msgid "Virtualization tools"
+msgstr "虚拟化工具"
+
+#: applications/const.py:34
+msgid "Custom"
+msgstr "自定义"
+
+#: applications/forms/remote_app.py:20
+msgid "Target URL"
+msgstr "目标URL"
+
+#: applications/forms/remote_app.py:23 applications/forms/remote_app.py:52
+#: applications/forms/remote_app.py:68
+msgid "Login username"
+msgstr "登录账号"
+
+#: applications/forms/remote_app.py:27 applications/forms/remote_app.py:56
+#: applications/forms/remote_app.py:72
+msgid "Login password"
+msgstr "登录密码"
+
+#: applications/forms/remote_app.py:33
+msgid "Database IP"
+msgstr "数据库IP"
+
+#: applications/forms/remote_app.py:36
+msgid "Database name"
+msgstr "数据库名"
+
+#: applications/forms/remote_app.py:39
+msgid "Database username"
+msgstr "数据库账号"
+
+#: applications/forms/remote_app.py:43
+msgid "Database password"
+msgstr "数据库密码"
+
+#: applications/forms/remote_app.py:49 applications/forms/remote_app.py:65
+msgid "Target address"
+msgstr "目标地址"
+
+#: applications/forms/remote_app.py:62
+msgid "Operating parameter"
+msgstr "运行参数"
+
+#: applications/forms/remote_app.py:105 applications/models/remote_app.py:23
+#: applications/templates/applications/remote_app_detail.html:57
+#: applications/templates/applications/remote_app_list.html:22
+#: applications/templates/applications/user_remote_app_list.html:18
+#: assets/forms/domain.py:15 assets/forms/label.py:13
+#: assets/models/asset.py:279 assets/models/authbook.py:27
+#: assets/templates/assets/admin_user_list.html:28
+#: assets/templates/assets/domain_detail.html:60
+#: assets/templates/assets/domain_list.html:26
+#: assets/templates/assets/label_list.html:16
+#: assets/templates/assets/system_user_list.html:33 audits/models.py:19
+#: audits/templates/audits/ftp_log_list.html:41
+#: audits/templates/audits/ftp_log_list.html:71
+#: perms/forms/asset_permission.py:46 perms/models/asset_permission.py:55
+#: perms/templates/perms/asset_permission_create_update.html:45
+#: perms/templates/perms/asset_permission_list.html:56
+#: perms/templates/perms/asset_permission_list.html:125
+#: terminal/backends/command/models.py:13 terminal/models.py:155
+#: terminal/templates/terminal/command_list.html:40
+#: terminal/templates/terminal/command_list.html:73
+#: terminal/templates/terminal/session_list.html:41
+#: terminal/templates/terminal/session_list.html:72
+#: xpack/plugins/change_auth_plan/forms.py:114
+#: xpack/plugins/change_auth_plan/models.py:409
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14
+#: xpack/plugins/cloud/models.py:187
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63
+#: xpack/plugins/orgs/templates/orgs/org_list.html:16
+msgid "Asset"
+msgstr "资产"
+
+#: applications/forms/remote_app.py:108 applications/models/remote_app.py:27
+#: applications/templates/applications/remote_app_detail.html:61
+#: applications/templates/applications/remote_app_list.html:23
+#: applications/templates/applications/user_remote_app_list.html:19
+#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168
+#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49
+#: audits/templates/audits/ftp_log_list.html:72
+#: perms/forms/asset_permission.py:52 perms/models/asset_permission.py:57
+#: perms/models/asset_permission.py:112
+#: perms/templates/perms/asset_permission_detail.html:140
+#: perms/templates/perms/asset_permission_list.html:58
+#: perms/templates/perms/asset_permission_list.html:79
+#: perms/templates/perms/asset_permission_list.html:131 templates/_nav.html:25
+#: terminal/backends/command/models.py:14 terminal/models.py:156
+#: terminal/templates/terminal/command_list.html:48
+#: terminal/templates/terminal/command_list.html:74
+#: terminal/templates/terminal/session_list.html:49
+#: terminal/templates/terminal/session_list.html:73
+#: xpack/plugins/orgs/templates/orgs/org_list.html:19
+msgid "System user"
+msgstr "系统用户"
+
+#: applications/models/remote_app.py:21
+#: applications/templates/applications/remote_app_detail.html:53
+#: applications/templates/applications/remote_app_list.html:20
+#: applications/templates/applications/user_remote_app_list.html:16
+#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146
+#: assets/models/base.py:26 assets/models/cluster.py:18
+#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
+#: assets/models/group.py:20 assets/models/label.py:18
+#: assets/templates/assets/admin_user_detail.html:56
+#: assets/templates/assets/admin_user_list.html:26
+#: assets/templates/assets/cmd_filter_detail.html:61
+#: assets/templates/assets/cmd_filter_list.html:24
+#: assets/templates/assets/domain_detail.html:56
+#: assets/templates/assets/domain_gateway_list.html:67
+#: assets/templates/assets/domain_list.html:25
+#: assets/templates/assets/label_list.html:14
+#: assets/templates/assets/system_user_detail.html:58
+#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37
+#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27
+#: orgs/models.py:12 perms/models/asset_permission.py:22
+#: perms/models/asset_permission.py:52 perms/models/remote_app_permission.py:33
+#: perms/templates/perms/asset_permission_detail.html:62
+#: perms/templates/perms/asset_permission_list.html:53
+#: perms/templates/perms/asset_permission_list.html:72
+#: perms/templates/perms/asset_permission_user.html:54
+#: perms/templates/perms/remote_app_permission_detail.html:62
+#: perms/templates/perms/remote_app_permission_list.html:14
+#: perms/templates/perms/remote_app_permission_remote_app.html:53
+#: perms/templates/perms/remote_app_permission_user.html:53
+#: settings/models.py:29
+#: settings/templates/settings/_ldap_list_users_modal.html:38
+#: settings/templates/settings/command_storage_create.html:41
+#: settings/templates/settings/replay_storage_create.html:44
+#: settings/templates/settings/terminal_setting.html:80
+#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22
+#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43
+#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
+#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13
+#: users/templates/users/user_detail.html:63
+#: users/templates/users/user_group_detail.html:55
+#: users/templates/users/user_group_list.html:12
+#: users/templates/users/user_list.html:23
+#: users/templates/users/user_profile.html:51
+#: users/templates/users/user_pubkey_update.html:53
+#: xpack/plugins/change_auth_plan/forms.py:97
+#: xpack/plugins/change_auth_plan/models.py:58
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
+#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119
+#: xpack/plugins/cloud/templates/cloud/account_detail.html:50
+#: xpack/plugins/cloud/templates/cloud/account_list.html:12
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12
+#: xpack/plugins/orgs/templates/orgs/org_detail.html:52
+#: xpack/plugins/orgs/templates/orgs/org_list.html:12
+msgid "Name"
+msgstr "名称"
+
+#: applications/models/remote_app.py:32
+#: applications/templates/applications/remote_app_detail.html:65
+#: applications/templates/applications/remote_app_list.html:21
+#: applications/templates/applications/user_remote_app_list.html:17
+msgid "App type"
+msgstr "应用类型"
+
+#: applications/models/remote_app.py:36
+#: applications/templates/applications/remote_app_detail.html:69
+msgid "App path"
+msgstr "应用路径"
+
+#: applications/models/remote_app.py:40
+msgid "Parameters"
+msgstr "参数"
+
+#: applications/models/remote_app.py:43
+#: applications/templates/applications/remote_app_detail.html:77
+#: assets/models/asset.py:109 assets/models/base.py:34
+#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
+#: assets/models/cmd_filter.py:58 assets/models/group.py:21
+#: assets/templates/assets/admin_user_detail.html:68
+#: assets/templates/assets/asset_detail.html:128
+#: assets/templates/assets/cmd_filter_detail.html:77
+#: assets/templates/assets/domain_detail.html:72
+#: assets/templates/assets/system_user_detail.html:100
+#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15
+#: perms/models/asset_permission.py:62 perms/models/asset_permission.py:115
+#: perms/models/remote_app_permission.py:40
+#: perms/templates/perms/asset_permission_detail.html:98
+#: perms/templates/perms/remote_app_permission_detail.html:90
+#: users/models/user.py:102 users/templates/users/user_detail.html:111
+#: xpack/plugins/change_auth_plan/models.py:103
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
+#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127
+msgid "Created by"
+msgstr "创建者"
+
+#: applications/models/remote_app.py:46
+#: applications/templates/applications/remote_app_detail.html:73
+#: assets/models/asset.py:110 assets/models/cluster.py:26
+#: assets/models/domain.py:23 assets/models/group.py:22
+#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64
+#: assets/templates/assets/cmd_filter_detail.html:69
+#: assets/templates/assets/domain_detail.html:68
+#: assets/templates/assets/system_user_detail.html:96
+#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64
+#: orgs/models.py:16 perms/models/asset_permission.py:63
+#: perms/models/asset_permission.py:116
+#: perms/models/remote_app_permission.py:41
+#: perms/templates/perms/asset_permission_detail.html:94
+#: perms/templates/perms/remote_app_permission_detail.html:86
+#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
+#: users/templates/users/user_group_detail.html:63
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105
+#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128
+#: xpack/plugins/cloud/templates/cloud/account_detail.html:66
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77
+#: xpack/plugins/orgs/templates/orgs/org_detail.html:60
+msgid "Date created"
+msgstr "创建日期"
+
+#: applications/models/remote_app.py:49
+#: applications/templates/applications/remote_app_detail.html:81
+#: applications/templates/applications/remote_app_list.html:24
+#: applications/templates/applications/user_remote_app_list.html:20
+#: assets/models/asset.py:111 assets/models/base.py:31
+#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
+#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
+#: assets/models/domain.py:53 assets/models/group.py:23
+#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
+#: assets/templates/assets/admin_user_list.html:32
+#: assets/templates/assets/asset_detail.html:136
+#: assets/templates/assets/cmd_filter_detail.html:65
+#: assets/templates/assets/cmd_filter_list.html:27
+#: assets/templates/assets/cmd_filter_rule_list.html:62
+#: assets/templates/assets/domain_detail.html:76
+#: assets/templates/assets/domain_gateway_list.html:72
+#: assets/templates/assets/domain_list.html:28
+#: assets/templates/assets/system_user_detail.html:104
+#: assets/templates/assets/system_user_list.html:37
+#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43
+#: orgs/models.py:17 perms/models/asset_permission.py:64
+#: perms/models/asset_permission.py:117
+#: perms/models/remote_app_permission.py:42
+#: perms/templates/perms/asset_permission_detail.html:102
+#: perms/templates/perms/remote_app_permission_detail.html:94
+#: settings/models.py:34 terminal/models.py:32
+#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
+#: users/models/user.py:94 users/templates/users/user_detail.html:127
+#: users/templates/users/user_group_detail.html:67
+#: users/templates/users/user_group_list.html:14
+#: users/templates/users/user_profile.html:134
+#: xpack/plugins/change_auth_plan/models.py:99
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
+#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125
+#: xpack/plugins/cloud/templates/cloud/account_detail.html:70
+#: xpack/plugins/cloud/templates/cloud/account_list.html:15
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16
+#: xpack/plugins/orgs/templates/orgs/org_detail.html:64
+#: xpack/plugins/orgs/templates/orgs/org_list.html:22
+msgid "Comment"
+msgstr "备注"
+
+#: applications/models/remote_app.py:53 perms/forms/remote_app_permission.py:37
+#: perms/models/remote_app_permission.py:36
+#: perms/templates/perms/remote_app_permission_create_update.html:48
+#: perms/templates/perms/remote_app_permission_detail.html:27
+#: perms/templates/perms/remote_app_permission_list.html:17
+#: perms/templates/perms/remote_app_permission_remote_app.html:26
+#: perms/templates/perms/remote_app_permission_user.html:26
+#: templates/_nav.html:35 templates/_nav_user.html:14
+msgid "RemoteApp"
+msgstr "远程应用"
+
+#: applications/templates/applications/remote_app_create_update.html:56
+#: assets/templates/assets/_system_user.html:75
+#: assets/templates/assets/admin_user_create_update.html:45
+#: assets/templates/assets/asset_bulk_update.html:23
+#: assets/templates/assets/asset_create.html:67
+#: assets/templates/assets/asset_update.html:71
+#: assets/templates/assets/cmd_filter_create_update.html:15
+#: assets/templates/assets/cmd_filter_rule_create_update.html:40
+#: assets/templates/assets/domain_create_update.html:16
+#: assets/templates/assets/gateway_create_update.html:58
+#: assets/templates/assets/label_create_update.html:18
+#: perms/templates/perms/asset_permission_create_update.html:83
+#: perms/templates/perms/remote_app_permission_create_update.html:83
+#: settings/templates/settings/basic_setting.html:61
+#: settings/templates/settings/command_storage_create.html:79
+#: settings/templates/settings/email_setting.html:62
+#: settings/templates/settings/ldap_setting.html:61
+#: settings/templates/settings/replay_storage_create.html:152
+#: settings/templates/settings/security_setting.html:70
+#: settings/templates/settings/terminal_setting.html:68
+#: terminal/templates/terminal/terminal_update.html:45
+#: users/templates/users/_user.html:50
+#: users/templates/users/user_bulk_update.html:23
+#: users/templates/users/user_detail.html:176
+#: users/templates/users/user_password_update.html:71
+#: users/templates/users/user_profile.html:204
+#: users/templates/users/user_profile_update.html:63
+#: users/templates/users/user_pubkey_update.html:70
+#: users/templates/users/user_pubkey_update.html:76
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71
+#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35
+#: xpack/plugins/interface/templates/interface/interface.html:72
+msgid "Reset"
+msgstr "重置"
+
+#: applications/templates/applications/remote_app_create_update.html:58
+#: assets/templates/assets/_system_user.html:76
+#: assets/templates/assets/admin_user_create_update.html:46
+#: assets/templates/assets/asset_bulk_update.html:24
+#: assets/templates/assets/asset_create.html:68
+#: assets/templates/assets/asset_list.html:113
+#: assets/templates/assets/asset_update.html:72
+#: assets/templates/assets/cmd_filter_create_update.html:16
+#: assets/templates/assets/cmd_filter_rule_create_update.html:41
+#: assets/templates/assets/domain_create_update.html:17
+#: assets/templates/assets/gateway_create_update.html:59
+#: assets/templates/assets/label_create_update.html:19
+#: audits/templates/audits/login_log_list.html:89
+#: perms/templates/perms/asset_permission_create_update.html:84
+#: perms/templates/perms/remote_app_permission_create_update.html:84
+#: settings/templates/settings/basic_setting.html:62
+#: settings/templates/settings/command_storage_create.html:80
+#: settings/templates/settings/email_setting.html:63
+#: settings/templates/settings/ldap_setting.html:64
+#: settings/templates/settings/replay_storage_create.html:153
+#: settings/templates/settings/security_setting.html:71
+#: settings/templates/settings/terminal_setting.html:70
+#: terminal/templates/terminal/command_list.html:103
+#: terminal/templates/terminal/session_list.html:126
+#: terminal/templates/terminal/terminal_update.html:46
+#: users/templates/users/_user.html:51
+#: users/templates/users/forgot_password.html:42
+#: users/templates/users/user_bulk_update.html:24
+#: users/templates/users/user_list.html:45
+#: users/templates/users/user_password_update.html:72
+#: users/templates/users/user_profile_update.html:64
+#: users/templates/users/user_pubkey_update.html:77
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72
+#: xpack/plugins/interface/templates/interface/interface.html:74
+msgid "Submit"
+msgstr "提交"
+
+#: applications/templates/applications/remote_app_detail.html:18
+#: assets/templates/assets/admin_user_assets.html:18
+#: assets/templates/assets/admin_user_detail.html:18
+#: assets/templates/assets/cmd_filter_detail.html:19
+#: assets/templates/assets/cmd_filter_rule_list.html:19
+#: assets/templates/assets/domain_detail.html:18
+#: assets/templates/assets/domain_gateway_list.html:20
+#: assets/templates/assets/system_user_asset.html:18
+#: assets/templates/assets/system_user_detail.html:18
+#: ops/templates/ops/adhoc_history.html:130
+#: ops/templates/ops/task_adhoc.html:116
+#: ops/templates/ops/task_history.html:136
+#: perms/templates/perms/asset_permission_asset.html:18
+#: perms/templates/perms/asset_permission_detail.html:18
+#: perms/templates/perms/asset_permission_user.html:18
+#: perms/templates/perms/remote_app_permission_detail.html:18
+#: perms/templates/perms/remote_app_permission_remote_app.html:17
+#: perms/templates/perms/remote_app_permission_user.html:17
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:17
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106
+#: xpack/plugins/change_auth_plan/views.py:83
+msgid "Detail"
+msgstr "详情"
+
+#: applications/templates/applications/remote_app_detail.html:21
+#: applications/templates/applications/remote_app_list.html:56
+#: assets/templates/assets/admin_user_detail.html:24
+#: assets/templates/assets/admin_user_list.html:88
+#: assets/templates/assets/asset_detail.html:27
+#: assets/templates/assets/asset_list.html:178
+#: assets/templates/assets/cmd_filter_detail.html:29
+#: assets/templates/assets/cmd_filter_list.html:58
+#: assets/templates/assets/cmd_filter_rule_list.html:86
+#: assets/templates/assets/domain_detail.html:24
+#: assets/templates/assets/domain_detail.html:103
+#: assets/templates/assets/domain_gateway_list.html:97
+#: assets/templates/assets/domain_list.html:54
+#: assets/templates/assets/label_list.html:39
+#: assets/templates/assets/system_user_detail.html:26
+#: assets/templates/assets/system_user_list.html:93 audits/models.py:33
+#: perms/templates/perms/asset_permission_detail.html:30
+#: perms/templates/perms/asset_permission_list.html:181
+#: perms/templates/perms/remote_app_permission_detail.html:30
+#: perms/templates/perms/remote_app_permission_list.html:59
+#: terminal/templates/terminal/terminal_detail.html:16
+#: terminal/templates/terminal/terminal_list.html:72
+#: users/templates/users/user_detail.html:25
+#: users/templates/users/user_group_detail.html:28
+#: users/templates/users/user_group_list.html:45
+#: users/templates/users/user_list.html:83
+#: users/templates/users/user_list.html:86
+#: users/templates/users/user_profile.html:177
+#: users/templates/users/user_profile.html:187
+#: users/templates/users/user_profile.html:196
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55
+#: xpack/plugins/cloud/templates/cloud/account_detail.html:23
+#: xpack/plugins/cloud/templates/cloud/account_list.html:39
+#: xpack/plugins/orgs/templates/orgs/org_detail.html:25
+#: xpack/plugins/orgs/templates/orgs/org_list.html:87
+msgid "Update"
+msgstr "更新"
+
+#: applications/templates/applications/remote_app_detail.html:25
+#: applications/templates/applications/remote_app_list.html:57
+#: assets/templates/assets/admin_user_detail.html:28
+#: assets/templates/assets/admin_user_list.html:89
+#: assets/templates/assets/asset_detail.html:31
+#: assets/templates/assets/asset_list.html:179
+#: assets/templates/assets/cmd_filter_detail.html:33
+#: assets/templates/assets/cmd_filter_list.html:59
+#: assets/templates/assets/cmd_filter_rule_list.html:87
+#: assets/templates/assets/domain_detail.html:28
+#: assets/templates/assets/domain_detail.html:104
+#: assets/templates/assets/domain_gateway_list.html:98
+#: assets/templates/assets/domain_list.html:55
+#: assets/templates/assets/label_list.html:40
+#: assets/templates/assets/system_user_detail.html:30
+#: assets/templates/assets/system_user_list.html:94 audits/models.py:34
+#: ops/templates/ops/task_list.html:64
+#: perms/templates/perms/asset_permission_detail.html:34
+#: perms/templates/perms/asset_permission_list.html:182
+#: perms/templates/perms/remote_app_permission_detail.html:34
+#: perms/templates/perms/remote_app_permission_list.html:60
+#: settings/templates/settings/terminal_setting.html:90
+#: settings/templates/settings/terminal_setting.html:112
+#: terminal/templates/terminal/terminal_list.html:74
+#: users/templates/users/user_detail.html:30
+#: users/templates/users/user_group_detail.html:32
+#: users/templates/users/user_group_list.html:47
+#: users/templates/users/user_list.html:91
+#: users/templates/users/user_list.html:95
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57
+#: xpack/plugins/cloud/templates/cloud/account_detail.html:27
+#: xpack/plugins/cloud/templates/cloud/account_list.html:41
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55
+#: xpack/plugins/orgs/templates/orgs/org_detail.html:29
+#: xpack/plugins/orgs/templates/orgs/org_list.html:89
+msgid "Delete"
+msgstr "删除"
+
+#: applications/templates/applications/remote_app_list.html:5
+msgid ""
+"Before using this feature, make sure that the application loader has been "
+"uploaded to the application server and successfully published as a RemoteApp "
+"application"
+msgstr ""
+"使用此功能前,请确保已将应用加载器上传到应用服务器并成功发布为一个RemoteApp应"
+"用"
+
+#: applications/templates/applications/remote_app_list.html:6
+msgid "Download application loader"
+msgstr "下载应用加载器"
+
+#: applications/templates/applications/remote_app_list.html:12
+#: applications/views/remote_app.py:47
+msgid "Create RemoteApp"
+msgstr "创建远程应用"
+
+#: applications/templates/applications/remote_app_list.html:25
+#: applications/templates/applications/user_remote_app_list.html:21
+#: assets/models/cmd_filter.py:54
+#: assets/templates/assets/admin_user_assets.html:52
+#: assets/templates/assets/admin_user_list.html:33
+#: assets/templates/assets/asset_asset_user_list.html:48
+#: assets/templates/assets/asset_list.html:96
+#: assets/templates/assets/cmd_filter_list.html:28
+#: assets/templates/assets/cmd_filter_rule_list.html:63
+#: assets/templates/assets/domain_gateway_list.html:73
+#: assets/templates/assets/domain_list.html:29
+#: assets/templates/assets/label_list.html:17
+#: assets/templates/assets/system_user_asset.html:54
+#: assets/templates/assets/system_user_list.html:38
+#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38
+#: audits/templates/audits/operate_log_list.html:41
+#: audits/templates/audits/operate_log_list.html:67
+#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
+#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34
+#: perms/forms/asset_permission.py:55 perms/models/asset_permission.py:26
+#: perms/models/asset_permission.py:58
+#: perms/templates/perms/asset_permission_create_update.html:50
+#: perms/templates/perms/asset_permission_list.html:60
+#: perms/templates/perms/asset_permission_list.html:134
+#: perms/templates/perms/remote_app_permission_list.html:19
+#: settings/templates/settings/terminal_setting.html:82
+#: settings/templates/settings/terminal_setting.html:104
+#: terminal/templates/terminal/session_list.html:81
+#: terminal/templates/terminal/terminal_list.html:36
+#: users/templates/users/user_group_list.html:15
+#: users/templates/users/user_list.html:29
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18
+#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20
+#: xpack/plugins/cloud/templates/cloud/account_list.html:16
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18
+#: xpack/plugins/orgs/templates/orgs/org_list.html:23
+msgid "Action"
+msgstr "动作"
+
+#: applications/templates/applications/user_remote_app_list.html:57
+#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19
+msgid "Connect"
+msgstr "连接"
+
+#: applications/views/remote_app.py:31 applications/views/remote_app.py:46
+#: applications/views/remote_app.py:67 applications/views/remote_app.py:84
+#: assets/models/user.py:134
+#: assets/templates/assets/_asset_group_bulk_update_modal.html:11
+#: assets/templates/assets/system_user_asset.html:22
+#: assets/templates/assets/system_user_detail.html:22
+#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
+#: assets/views/admin_user.py:63 assets/views/admin_user.py:78
+#: assets/views/admin_user.py:102 assets/views/asset.py:51
+#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145
+#: assets/views/asset.py:162 assets/views/asset.py:186
+#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46
+#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78
+#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130
+#: assets/views/cmd_filter.py:163 assets/views/domain.py:29
+#: assets/views/domain.py:45 assets/views/domain.py:61
+#: assets/views/domain.py:74 assets/views/domain.py:98
+#: assets/views/domain.py:126 assets/views/domain.py:145
+#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69
+#: assets/views/system_user.py:28 assets/views/system_user.py:44
+#: assets/views/system_user.py:60 assets/views/system_user.py:74
+#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65
+msgid "Assets"
+msgstr "资产管理"
+
+#: applications/views/remote_app.py:32
+msgid "RemoteApp list"
+msgstr "远程应用列表"
+
+#: applications/views/remote_app.py:68
+msgid "Update RemoteApp"
+msgstr "更新远程应用"
+
+#: applications/views/remote_app.py:85
+msgid "RemoteApp detail"
+msgstr "远程应用详情"
+
+#: applications/views/remote_app.py:96
+msgid "My RemoteApp"
+msgstr "我的远程应用"
+
#: assets/api/asset.py:112
msgid "Please select assets that need to be updated"
msgstr "请选择需要更新的资产"
@@ -36,7 +603,8 @@ msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133
#: assets/templates/assets/asset_detail.html:194
#: assets/templates/assets/asset_detail.html:202
-#: assets/templates/assets/system_user_asset.html:95 perms/models.py:51
+#: assets/templates/assets/system_user_asset.html:95
+#: perms/models/asset_permission.py:56
#: xpack/plugins/change_auth_plan/models.py:69
msgid "Nodes"
msgstr "节点管理"
@@ -72,8 +640,9 @@ msgstr "网域"
#: assets/forms/asset.py:41 assets/forms/asset.py:63 assets/forms/asset.py:77
#: assets/forms/asset.py:112 assets/models/node.py:31
#: assets/templates/assets/asset_create.html:30
-#: assets/templates/assets/asset_update.html:35 perms/forms.py:45
-#: perms/forms.py:55 perms/models.py:105
+#: assets/templates/assets/asset_update.html:35
+#: perms/forms/asset_permission.py:49 perms/forms/asset_permission.py:59
+#: perms/models/asset_permission.py:110
#: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:78
#: perms/templates/perms/asset_permission_list.html:128
@@ -111,36 +680,6 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets"
msgstr "选择资产"
-#: assets/forms/domain.py:15 assets/forms/label.py:13
-#: assets/models/asset.py:279 assets/models/authbook.py:27
-#: assets/templates/assets/admin_user_list.html:28
-#: assets/templates/assets/domain_detail.html:60
-#: assets/templates/assets/domain_list.html:26
-#: assets/templates/assets/label_list.html:16
-#: assets/templates/assets/system_user_list.html:33 audits/models.py:19
-#: audits/templates/audits/ftp_log_list.html:41
-#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42
-#: perms/models.py:50
-#: perms/templates/perms/asset_permission_create_update.html:45
-#: perms/templates/perms/asset_permission_list.html:56
-#: perms/templates/perms/asset_permission_list.html:125
-#: terminal/backends/command/models.py:13 terminal/models.py:155
-#: terminal/templates/terminal/command_list.html:40
-#: terminal/templates/terminal/command_list.html:73
-#: terminal/templates/terminal/session_list.html:41
-#: terminal/templates/terminal/session_list.html:72
-#: xpack/plugins/change_auth_plan/forms.py:114
-#: xpack/plugins/change_auth_plan/models.py:409
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14
-#: xpack/plugins/cloud/models.py:187
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63
-#: xpack/plugins/orgs/templates/orgs/org_list.html:16
-msgid "Asset"
-msgstr "资产"
-
#: assets/forms/domain.py:51
msgid "Password should not contain special characters"
msgstr "不能包含特殊字符"
@@ -149,54 +688,6 @@ msgstr "不能包含特殊字符"
msgid "SSH gateway support proxy SSH,RDP,VNC"
msgstr "SSH网关,支持代理SSH,RDP和VNC"
-#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146
-#: assets/models/base.py:26 assets/models/cluster.py:18
-#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
-#: assets/models/group.py:20 assets/models/label.py:18
-#: assets/templates/assets/admin_user_detail.html:56
-#: assets/templates/assets/admin_user_list.html:26
-#: assets/templates/assets/cmd_filter_detail.html:61
-#: assets/templates/assets/cmd_filter_list.html:24
-#: assets/templates/assets/domain_detail.html:56
-#: assets/templates/assets/domain_gateway_list.html:67
-#: assets/templates/assets/domain_list.html:25
-#: assets/templates/assets/label_list.html:14
-#: assets/templates/assets/system_user_detail.html:58
-#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37
-#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27
-#: orgs/models.py:12 perms/models.py:17 perms/models.py:47
-#: perms/templates/perms/asset_permission_detail.html:62
-#: perms/templates/perms/asset_permission_list.html:53
-#: perms/templates/perms/asset_permission_list.html:72
-#: perms/templates/perms/asset_permission_user.html:54 settings/models.py:29
-#: settings/templates/settings/_ldap_list_users_modal.html:38
-#: settings/templates/settings/command_storage_create.html:41
-#: settings/templates/settings/replay_storage_create.html:44
-#: settings/templates/settings/terminal_setting.html:80
-#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22
-#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43
-#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
-#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13
-#: users/templates/users/user_detail.html:63
-#: users/templates/users/user_group_detail.html:55
-#: users/templates/users/user_group_list.html:12
-#: users/templates/users/user_list.html:23
-#: users/templates/users/user_profile.html:51
-#: users/templates/users/user_pubkey_update.html:53
-#: xpack/plugins/change_auth_plan/forms.py:97
-#: xpack/plugins/change_auth_plan/models.py:58
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
-#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119
-#: xpack/plugins/cloud/templates/cloud/account_detail.html:50
-#: xpack/plugins/cloud/templates/cloud/account_list.html:12
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12
-#: xpack/plugins/orgs/templates/orgs/org_detail.html:52
-#: xpack/plugins/orgs/templates/orgs/org_list.html:12
-msgid "Name"
-msgstr "名称"
-
#: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147
#: assets/models/base.py:27
#: assets/templates/assets/_asset_user_auth_modal.html:15
@@ -212,6 +703,7 @@ msgstr "名称"
#: authentication/templates/authentication/new_login.html:90
#: ops/models/adhoc.py:164 perms/templates/perms/asset_permission_list.html:74
#: perms/templates/perms/asset_permission_user.html:55
+#: perms/templates/perms/remote_app_permission_user.html:54
#: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:13
#: users/models/user.py:59 users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:67
@@ -434,79 +926,6 @@ msgstr "主机名原始"
msgid "Labels"
msgstr "标签管理"
-#: assets/models/asset.py:109 assets/models/base.py:34
-#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
-#: assets/models/cmd_filter.py:58 assets/models/group.py:21
-#: assets/templates/assets/admin_user_detail.html:68
-#: assets/templates/assets/asset_detail.html:128
-#: assets/templates/assets/cmd_filter_detail.html:77
-#: assets/templates/assets/domain_detail.html:72
-#: assets/templates/assets/system_user_detail.html:100
-#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57
-#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98
-#: users/models/user.py:102 users/templates/users/user_detail.html:111
-#: xpack/plugins/change_auth_plan/models.py:103
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
-#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127
-msgid "Created by"
-msgstr "创建者"
-
-#: assets/models/asset.py:110 assets/models/cluster.py:26
-#: assets/models/domain.py:23 assets/models/group.py:22
-#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64
-#: assets/templates/assets/cmd_filter_detail.html:69
-#: assets/templates/assets/domain_detail.html:68
-#: assets/templates/assets/system_user_detail.html:96
-#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64
-#: orgs/models.py:16 perms/models.py:58 perms/models.py:111
-#: perms/templates/perms/asset_permission_detail.html:94
-#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
-#: users/templates/users/user_group_detail.html:63
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105
-#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128
-#: xpack/plugins/cloud/templates/cloud/account_detail.html:66
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77
-#: xpack/plugins/orgs/templates/orgs/org_detail.html:60
-msgid "Date created"
-msgstr "创建日期"
-
-#: assets/models/asset.py:111 assets/models/base.py:31
-#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
-#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
-#: assets/models/domain.py:53 assets/models/group.py:23
-#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
-#: assets/templates/assets/admin_user_list.html:32
-#: assets/templates/assets/asset_detail.html:136
-#: assets/templates/assets/cmd_filter_detail.html:65
-#: assets/templates/assets/cmd_filter_list.html:27
-#: assets/templates/assets/cmd_filter_rule_list.html:62
-#: assets/templates/assets/domain_detail.html:76
-#: assets/templates/assets/domain_gateway_list.html:72
-#: assets/templates/assets/domain_list.html:28
-#: assets/templates/assets/system_user_detail.html:104
-#: assets/templates/assets/system_user_list.html:37
-#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43
-#: orgs/models.py:17 perms/models.py:59 perms/models.py:112
-#: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34
-#: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63
-#: users/models/group.py:15 users/models/user.py:94
-#: users/templates/users/user_detail.html:127
-#: users/templates/users/user_group_detail.html:67
-#: users/templates/users/user_group_list.html:14
-#: users/templates/users/user_profile.html:134
-#: xpack/plugins/change_auth_plan/models.py:99
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
-#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125
-#: xpack/plugins/cloud/templates/cloud/account_detail.html:70
-#: xpack/plugins/cloud/templates/cloud/account_list.html:15
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16
-#: xpack/plugins/orgs/templates/orgs/org_detail.html:64
-#: xpack/plugins/orgs/templates/orgs/org_list.html:22
-msgid "Comment"
-msgstr "备注"
-
#: assets/models/asset.py:117 assets/models/base.py:38
#: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/system_user_list.html:35
@@ -639,6 +1058,7 @@ msgstr "过滤器"
#: assets/models/cmd_filter.py:50
#: assets/templates/assets/cmd_filter_rule_list.html:58
#: audits/templates/audits/login_log_list.html:52
+#: perms/templates/perms/remote_app_permission_remote_app.html:54
#: settings/templates/settings/command_storage_create.html:31
#: settings/templates/settings/replay_storage_create.html:31
#: settings/templates/settings/terminal_setting.html:81
@@ -665,42 +1085,6 @@ msgstr "内容"
msgid "One line one command"
msgstr "每行一个命令"
-#: assets/models/cmd_filter.py:54
-#: assets/templates/assets/admin_user_assets.html:52
-#: assets/templates/assets/admin_user_list.html:33
-#: assets/templates/assets/asset_asset_user_list.html:48
-#: assets/templates/assets/asset_list.html:96
-#: assets/templates/assets/cmd_filter_list.html:28
-#: assets/templates/assets/cmd_filter_rule_list.html:63
-#: assets/templates/assets/domain_gateway_list.html:73
-#: assets/templates/assets/domain_list.html:29
-#: assets/templates/assets/label_list.html:17
-#: assets/templates/assets/system_user_asset.html:54
-#: assets/templates/assets/system_user_list.html:38
-#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38
-#: audits/templates/audits/operate_log_list.html:41
-#: audits/templates/audits/operate_log_list.html:67
-#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
-#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34
-#: perms/forms.py:51 perms/models.py:21 perms/models.py:53
-#: perms/templates/perms/asset_permission_create_update.html:50
-#: perms/templates/perms/asset_permission_list.html:60
-#: perms/templates/perms/asset_permission_list.html:134
-#: settings/templates/settings/terminal_setting.html:82
-#: settings/templates/settings/terminal_setting.html:104
-#: terminal/templates/terminal/session_list.html:81
-#: terminal/templates/terminal/terminal_list.html:36
-#: users/templates/users/user_group_list.html:15
-#: users/templates/users/user_list.html:29
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20
-#: xpack/plugins/cloud/templates/cloud/account_list.html:16
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18
-#: xpack/plugins/orgs/templates/orgs/org_list.html:23
-msgid "Action"
-msgstr "动作"
-
#: assets/models/cmd_filter.py:64
msgid "Command filter rule"
msgstr "命令过滤规则"
@@ -728,13 +1112,16 @@ msgstr "默认资产组"
#: audits/templates/audits/password_change_log_list.html:33
#: audits/templates/audits/password_change_log_list.html:50
#: ops/templates/ops/command_execution_list.html:35
-#: ops/templates/ops/command_execution_list.html:60 perms/forms.py:36
-#: perms/models.py:48
+#: ops/templates/ops/command_execution_list.html:60
+#: perms/forms/asset_permission.py:40 perms/forms/remote_app_permission.py:31
+#: perms/models/asset_permission.py:53 perms/models/remote_app_permission.py:34
#: perms/templates/perms/asset_permission_create_update.html:41
#: perms/templates/perms/asset_permission_list.html:54
-#: perms/templates/perms/asset_permission_list.html:119 templates/index.html:87
-#: terminal/backends/command/models.py:12 terminal/models.py:154
-#: terminal/templates/terminal/command_list.html:32
+#: perms/templates/perms/asset_permission_list.html:119
+#: perms/templates/perms/remote_app_permission_create_update.html:43
+#: perms/templates/perms/remote_app_permission_list.html:15
+#: templates/index.html:87 terminal/backends/command/models.py:12
+#: terminal/models.py:154 terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:283
@@ -772,29 +1159,6 @@ msgstr "自动登录"
msgid "Manually login"
msgstr "手动登录"
-#: assets/models/user.py:134
-#: assets/templates/assets/_asset_group_bulk_update_modal.html:11
-#: assets/templates/assets/system_user_asset.html:22
-#: assets/templates/assets/system_user_detail.html:22
-#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
-#: assets/views/admin_user.py:63 assets/views/admin_user.py:78
-#: assets/views/admin_user.py:102 assets/views/asset.py:51
-#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145
-#: assets/views/asset.py:162 assets/views/asset.py:186
-#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46
-#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78
-#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130
-#: assets/views/cmd_filter.py:163 assets/views/domain.py:29
-#: assets/views/domain.py:45 assets/views/domain.py:61
-#: assets/views/domain.py:74 assets/views/domain.py:98
-#: assets/views/domain.py:126 assets/views/domain.py:145
-#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69
-#: assets/views/system_user.py:28 assets/views/system_user.py:44
-#: assets/views/system_user.py:60 assets/views/system_user.py:74
-#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65
-msgid "Assets"
-msgstr "资产管理"
-
#: assets/models/user.py:137 assets/templates/assets/_system_user.html:59
#: assets/templates/assets/system_user_detail.html:122
#: assets/templates/assets/system_user_update.html:10
@@ -814,23 +1178,6 @@ msgstr "Shell"
msgid "Login mode"
msgstr "登录模式"
-#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168
-#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49
-#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48
-#: perms/models.py:52 perms/models.py:107
-#: perms/templates/perms/asset_permission_detail.html:140
-#: perms/templates/perms/asset_permission_list.html:58
-#: perms/templates/perms/asset_permission_list.html:79
-#: perms/templates/perms/asset_permission_list.html:131 templates/_nav.html:25
-#: terminal/backends/command/models.py:14 terminal/models.py:156
-#: terminal/templates/terminal/command_list.html:48
-#: terminal/templates/terminal/command_list.html:74
-#: terminal/templates/terminal/session_list.html:49
-#: terminal/templates/terminal/session_list.html:73
-#: xpack/plugins/orgs/templates/orgs/org_list.html:19
-msgid "System user"
-msgstr "系统用户"
-
#: assets/models/utils.py:29
#, python-format
msgid "%(value)s is not an even number"
@@ -983,9 +1330,9 @@ msgid "Asset user auth"
msgstr "资产用户信息"
#: assets/templates/assets/_asset_user_view_auth_modal.html:14
-#: assets/templates/assets/_otp_verify_modal.html:8 audits/models.py:99
-#: audits/templates/audits/login_log_list.html:56 users/forms.py:142
-#: users/models/user.py:83 users/templates/users/first_login.html:45
+#: audits/models.py:99 audits/templates/audits/login_log_list.html:56
+#: users/forms.py:142 users/models/user.py:83
+#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
@@ -1045,15 +1392,12 @@ msgstr "SSH端口"
msgid "If use nat, set the ssh real port"
msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口"
-#: assets/templates/assets/_otp_verify_modal.html:4
-msgid "MFA Confirm"
-msgstr "确认"
-
#: assets/templates/assets/_system_user.html:37
#: assets/templates/assets/asset_create.html:16
#: assets/templates/assets/asset_update.html:21
#: assets/templates/assets/gateway_create_update.html:37
#: perms/templates/perms/asset_permission_create_update.html:38
+#: perms/templates/perms/remote_app_permission_create_update.html:39
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:43
msgid "Basic"
msgstr "基本"
@@ -1075,108 +1419,18 @@ msgstr "自动生成密钥"
#: assets/templates/assets/asset_update.html:64
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:53
+#: perms/templates/perms/remote_app_permission_create_update.html:52
#: terminal/templates/terminal/terminal_update.html:40
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67
msgid "Other"
msgstr "其它"
-#: assets/templates/assets/_system_user.html:75
-#: assets/templates/assets/admin_user_create_update.html:45
-#: assets/templates/assets/asset_bulk_update.html:23
-#: assets/templates/assets/asset_create.html:67
-#: assets/templates/assets/asset_update.html:71
-#: assets/templates/assets/cmd_filter_create_update.html:15
-#: assets/templates/assets/cmd_filter_rule_create_update.html:40
-#: assets/templates/assets/domain_create_update.html:16
-#: assets/templates/assets/gateway_create_update.html:58
-#: assets/templates/assets/label_create_update.html:18
-#: perms/templates/perms/asset_permission_create_update.html:83
-#: settings/templates/settings/basic_setting.html:61
-#: settings/templates/settings/command_storage_create.html:79
-#: settings/templates/settings/email_setting.html:62
-#: settings/templates/settings/ldap_setting.html:61
-#: settings/templates/settings/replay_storage_create.html:152
-#: settings/templates/settings/security_setting.html:70
-#: settings/templates/settings/terminal_setting.html:68
-#: terminal/templates/terminal/terminal_update.html:45
-#: users/templates/users/_user.html:50
-#: users/templates/users/user_bulk_update.html:23
-#: users/templates/users/user_detail.html:176
-#: users/templates/users/user_password_update.html:71
-#: users/templates/users/user_profile.html:204
-#: users/templates/users/user_profile_update.html:63
-#: users/templates/users/user_pubkey_update.html:70
-#: users/templates/users/user_pubkey_update.html:76
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71
-#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35
-#: xpack/plugins/interface/templates/interface/interface.html:72
-msgid "Reset"
-msgstr "重置"
-
-#: assets/templates/assets/_system_user.html:76
-#: assets/templates/assets/admin_user_create_update.html:46
-#: assets/templates/assets/asset_bulk_update.html:24
-#: assets/templates/assets/asset_create.html:68
-#: assets/templates/assets/asset_list.html:113
-#: assets/templates/assets/asset_update.html:72
-#: assets/templates/assets/cmd_filter_create_update.html:16
-#: assets/templates/assets/cmd_filter_rule_create_update.html:41
-#: assets/templates/assets/domain_create_update.html:17
-#: assets/templates/assets/gateway_create_update.html:59
-#: assets/templates/assets/label_create_update.html:19
-#: audits/templates/audits/login_log_list.html:89
-#: perms/templates/perms/asset_permission_create_update.html:84
-#: settings/templates/settings/basic_setting.html:62
-#: settings/templates/settings/command_storage_create.html:80
-#: settings/templates/settings/email_setting.html:63
-#: settings/templates/settings/ldap_setting.html:64
-#: settings/templates/settings/replay_storage_create.html:153
-#: settings/templates/settings/security_setting.html:71
-#: settings/templates/settings/terminal_setting.html:70
-#: terminal/templates/terminal/command_list.html:103
-#: terminal/templates/terminal/session_list.html:126
-#: terminal/templates/terminal/terminal_update.html:46
-#: users/templates/users/_user.html:51
-#: users/templates/users/forgot_password.html:42
-#: users/templates/users/user_bulk_update.html:24
-#: users/templates/users/user_list.html:45
-#: users/templates/users/user_password_update.html:72
-#: users/templates/users/user_profile_update.html:64
-#: users/templates/users/user_pubkey_update.html:77
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72
-#: xpack/plugins/interface/templates/interface/interface.html:74
-msgid "Submit"
-msgstr "提交"
-
#: assets/templates/assets/_user_asset_detail_modal.html:11
#: assets/templates/assets/asset_asset_user_list.html:13
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187
msgid "Asset detail"
msgstr "资产详情"
-#: assets/templates/assets/admin_user_assets.html:18
-#: assets/templates/assets/admin_user_detail.html:18
-#: assets/templates/assets/cmd_filter_detail.html:19
-#: assets/templates/assets/cmd_filter_rule_list.html:19
-#: assets/templates/assets/domain_detail.html:18
-#: assets/templates/assets/domain_gateway_list.html:20
-#: assets/templates/assets/system_user_asset.html:18
-#: assets/templates/assets/system_user_detail.html:18
-#: ops/templates/ops/adhoc_history.html:130
-#: ops/templates/ops/task_adhoc.html:116
-#: ops/templates/ops/task_history.html:136
-#: perms/templates/perms/asset_permission_asset.html:18
-#: perms/templates/perms/asset_permission_detail.html:18
-#: perms/templates/perms/asset_permission_user.html:18
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:17
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106
-#: xpack/plugins/change_auth_plan/views.py:83
-msgid "Detail"
-msgstr "详情"
-
#: assets/templates/assets/admin_user_assets.html:21
#: assets/templates/assets/admin_user_detail.html:21
msgid "Assets list"
@@ -1192,6 +1446,7 @@ msgstr "资产列表"
#: assets/templates/assets/system_user_asset.html:66
#: assets/templates/assets/system_user_detail.html:116
#: perms/templates/perms/asset_permission_detail.html:114
+#: perms/templates/perms/remote_app_permission_detail.html:106
msgid "Quick update"
msgstr "快速更新"
@@ -1240,77 +1495,6 @@ msgstr "更新成功"
msgid "Update failed!"
msgstr "更新失败"
-#: assets/templates/assets/admin_user_detail.html:24
-#: assets/templates/assets/admin_user_list.html:88
-#: assets/templates/assets/asset_detail.html:27
-#: assets/templates/assets/asset_list.html:178
-#: assets/templates/assets/cmd_filter_detail.html:29
-#: assets/templates/assets/cmd_filter_list.html:58
-#: assets/templates/assets/cmd_filter_rule_list.html:86
-#: assets/templates/assets/domain_detail.html:24
-#: assets/templates/assets/domain_detail.html:103
-#: assets/templates/assets/domain_gateway_list.html:97
-#: assets/templates/assets/domain_list.html:54
-#: assets/templates/assets/label_list.html:39
-#: assets/templates/assets/system_user_detail.html:26
-#: assets/templates/assets/system_user_list.html:93 audits/models.py:33
-#: perms/templates/perms/asset_permission_detail.html:30
-#: perms/templates/perms/asset_permission_list.html:181
-#: terminal/templates/terminal/terminal_detail.html:16
-#: terminal/templates/terminal/terminal_list.html:72
-#: users/templates/users/user_detail.html:25
-#: users/templates/users/user_group_detail.html:28
-#: users/templates/users/user_group_list.html:45
-#: users/templates/users/user_list.html:83
-#: users/templates/users/user_list.html:86
-#: users/templates/users/user_profile.html:177
-#: users/templates/users/user_profile.html:187
-#: users/templates/users/user_profile.html:196
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55
-#: xpack/plugins/cloud/templates/cloud/account_detail.html:23
-#: xpack/plugins/cloud/templates/cloud/account_list.html:39
-#: xpack/plugins/orgs/templates/orgs/org_detail.html:25
-#: xpack/plugins/orgs/templates/orgs/org_list.html:87
-msgid "Update"
-msgstr "更新"
-
-#: assets/templates/assets/admin_user_detail.html:28
-#: assets/templates/assets/admin_user_list.html:89
-#: assets/templates/assets/asset_detail.html:31
-#: assets/templates/assets/asset_list.html:179
-#: assets/templates/assets/cmd_filter_detail.html:33
-#: assets/templates/assets/cmd_filter_list.html:59
-#: assets/templates/assets/cmd_filter_rule_list.html:87
-#: assets/templates/assets/domain_detail.html:28
-#: assets/templates/assets/domain_detail.html:104
-#: assets/templates/assets/domain_gateway_list.html:98
-#: assets/templates/assets/domain_list.html:55
-#: assets/templates/assets/label_list.html:40
-#: assets/templates/assets/system_user_detail.html:30
-#: assets/templates/assets/system_user_list.html:94 audits/models.py:34
-#: ops/templates/ops/task_list.html:64
-#: perms/templates/perms/asset_permission_detail.html:34
-#: perms/templates/perms/asset_permission_list.html:182
-#: settings/templates/settings/terminal_setting.html:90
-#: settings/templates/settings/terminal_setting.html:112
-#: terminal/templates/terminal/terminal_list.html:74
-#: users/templates/users/user_detail.html:30
-#: users/templates/users/user_group_detail.html:32
-#: users/templates/users/user_group_list.html:47
-#: users/templates/users/user_list.html:91
-#: users/templates/users/user_list.html:95
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33
-#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57
-#: xpack/plugins/cloud/templates/cloud/account_detail.html:27
-#: xpack/plugins/cloud/templates/cloud/account_list.html:41
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55
-#: xpack/plugins/orgs/templates/orgs/org_detail.html:29
-#: xpack/plugins/orgs/templates/orgs/org_list.html:89
-msgid "Delete"
-msgstr "删除"
-
#: assets/templates/assets/admin_user_detail.html:83
msgid "Replace node assets admin user with this"
msgstr "替换资产的管理员"
@@ -1405,10 +1589,13 @@ msgid "Date joined"
msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:154
-#: assets/templates/assets/user_asset_list.html:46 perms/models.py:54
-#: perms/models.py:108
+#: assets/templates/assets/user_asset_list.html:46
+#: perms/models/asset_permission.py:59 perms/models/asset_permission.py:113
+#: perms/models/remote_app_permission.py:37
#: perms/templates/perms/asset_permission_create_update.html:55
#: perms/templates/perms/asset_permission_detail.html:120
+#: perms/templates/perms/remote_app_permission_create_update.html:54
+#: perms/templates/perms/remote_app_permission_detail.html:112
#: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18
#: users/templates/users/user_detail.html:144
@@ -1783,10 +1970,6 @@ msgstr "删除系统用户"
msgid "System Users Deleting failed."
msgstr "系统用户删除失败"
-#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19
-msgid "Connect"
-msgstr "连接"
-
#: assets/views/admin_user.py:30
msgid "Admin user list"
msgstr "管理用户列表"
@@ -2009,9 +2192,11 @@ msgstr "登录日期"
#: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/command_execution_list.html:66
-#: ops/templates/ops/task_history.html:58 perms/models.py:55
-#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:165
-#: terminal/templates/terminal/session_list.html:78
+#: ops/templates/ops/task_history.html:58 perms/models/asset_permission.py:60
+#: perms/models/remote_app_permission.py:38
+#: perms/templates/perms/asset_permission_detail.html:86
+#: perms/templates/perms/remote_app_permission_detail.html:78
+#: terminal/models.py:165 terminal/templates/terminal/session_list.html:78
#: xpack/plugins/change_auth_plan/models.py:246
#: xpack/plugins/change_auth_plan/models.py:416
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
@@ -2021,6 +2206,7 @@ msgstr "开始日期"
#: audits/templates/audits/login_log_list.html:28
#: perms/templates/perms/asset_permission_user.html:88
+#: perms/templates/perms/remote_app_permission_user.html:87
msgid "Select user"
msgstr "选择用户"
@@ -2071,23 +2257,23 @@ msgid "Datetime"
msgstr "日期"
#: audits/views.py:85 audits/views.py:129 audits/views.py:165
-#: audits/views.py:209 audits/views.py:241 templates/_nav.html:72
+#: audits/views.py:209 audits/views.py:241 templates/_nav.html:83
msgid "Audits"
msgstr "日志审计"
-#: audits/views.py:86 templates/_nav.html:76
+#: audits/views.py:86 templates/_nav.html:87
msgid "FTP log"
msgstr "FTP日志"
-#: audits/views.py:130 templates/_nav.html:77
+#: audits/views.py:130 templates/_nav.html:88
msgid "Operate log"
msgstr "操作日志"
-#: audits/views.py:166 templates/_nav.html:78
+#: audits/views.py:166 templates/_nav.html:89
msgid "Password change log"
msgstr "改密日志"
-#: audits/views.py:210 templates/_nav.html:75
+#: audits/views.py:210 templates/_nav.html:86
msgid "Login log"
msgstr "登录日志"
@@ -2712,7 +2898,7 @@ msgstr "更新任务内容: {}"
msgid "Ops"
msgstr "作业中心"
-#: ops/views/adhoc.py:45 templates/_nav.html:66
+#: ops/views/adhoc.py:45 templates/_nav.html:77
msgid "Task list"
msgstr "任务列表"
@@ -2724,7 +2910,7 @@ msgstr "执行历史"
msgid "Command execution list"
msgstr "命令执行列表"
-#: ops/views/command.py:69 templates/_nav_user.html:9
+#: ops/views/command.py:69 templates/_nav_user.html:21
msgid "Command execution"
msgstr "命令执行"
@@ -2744,46 +2930,59 @@ msgstr "上传文件"
msgid "Download file"
msgstr "下载文件"
-#: perms/forms.py:39 perms/models.py:49 perms/models.py:106
+#: perms/forms/asset_permission.py:43 perms/forms/remote_app_permission.py:34
+#: perms/models/asset_permission.py:54 perms/models/asset_permission.py:111
+#: perms/models/remote_app_permission.py:35
#: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:75
-#: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14
-#: users/forms.py:253 users/models/group.py:26 users/models/user.py:67
-#: users/templates/users/_select_user_modal.html:16
+#: perms/templates/perms/asset_permission_list.html:122
+#: perms/templates/perms/remote_app_permission_list.html:16
+#: templates/_nav.html:14 users/forms.py:253 users/models/group.py:26
+#: users/models/user.py:67 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:213
#: users/templates/users/user_list.html:26
#: xpack/plugins/orgs/templates/orgs/org_list.html:15
msgid "User group"
msgstr "用户组"
-#: perms/forms.py:58
+#: perms/forms/asset_permission.py:62
msgid ""
"Tips: The RDP protocol does not support separate controls for uploading or "
"downloading files"
msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
-#: perms/forms.py:68
+#: perms/forms/asset_permission.py:72 perms/forms/remote_app_permission.py:47
msgid "User or group at least one required"
msgstr "用户和用户组至少选一个"
-#: perms/forms.py:77
+#: perms/forms/asset_permission.py:81
msgid "Asset or group at least one required"
msgstr "资产和节点至少选一个"
-#: perms/models.py:56 perms/models.py:109
+#: perms/models/asset_permission.py:61 perms/models/asset_permission.py:114
+#: perms/models/remote_app_permission.py:39
#: perms/templates/perms/asset_permission_detail.html:90
+#: perms/templates/perms/remote_app_permission_detail.html:82
#: users/models/user.py:99 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:116
msgid "Date expired"
msgstr "失效日期"
-#: perms/models.py:65 perms/models.py:118 templates/_nav.html:34
+#: perms/models/asset_permission.py:70 perms/models/asset_permission.py:123
+#: templates/_nav.html:42
msgid "Asset permission"
msgstr "资产授权"
+#: perms/models/remote_app_permission.py:48 templates/_nav.html:45
+msgid "RemoteApp permission"
+msgstr "远程应用授权"
+
#: perms/templates/perms/asset_permission_asset.html:22
#: perms/templates/perms/asset_permission_detail.html:22
#: perms/templates/perms/asset_permission_user.html:22
+#: perms/templates/perms/remote_app_permission_detail.html:22
+#: perms/templates/perms/remote_app_permission_remote_app.html:21
+#: perms/templates/perms/remote_app_permission_user.html:21
msgid "Users and user groups"
msgstr "用户或用户组"
@@ -2804,6 +3003,9 @@ msgstr "添加资产"
#: perms/templates/perms/asset_permission_detail.html:157
#: perms/templates/perms/asset_permission_user.html:97
#: perms/templates/perms/asset_permission_user.html:125
+#: perms/templates/perms/remote_app_permission_remote_app.html:96
+#: perms/templates/perms/remote_app_permission_user.html:96
+#: perms/templates/perms/remote_app_permission_user.html:124
#: settings/templates/settings/terminal_setting.html:95
#: settings/templates/settings/terminal_setting.html:117
#: users/templates/users/user_group_detail.html:95
@@ -2824,17 +3026,20 @@ msgid "Join"
msgstr "加入"
#: perms/templates/perms/asset_permission_create_update.html:61
+#: perms/templates/perms/remote_app_permission_create_update.html:60
msgid "Validity period"
msgstr "有效期"
#: perms/templates/perms/asset_permission_detail.html:66
+#: perms/templates/perms/remote_app_permission_detail.html:66
#: xpack/plugins/license/templates/license/license_detail.html:76
msgid "User count"
msgstr "用户数量"
#: perms/templates/perms/asset_permission_detail.html:70
+#: perms/templates/perms/remote_app_permission_detail.html:70
msgid "User group count"
-msgstr "用户组列表"
+msgstr "用户组数量"
#: perms/templates/perms/asset_permission_detail.html:74
#: xpack/plugins/license/templates/license/license_detail.html:72
@@ -2854,11 +3059,13 @@ msgid "Select system users"
msgstr "选择系统用户"
#: perms/templates/perms/asset_permission_list.html:46
+#: perms/templates/perms/remote_app_permission_list.html:6
msgid "Create permission"
msgstr "创建授权规则"
#: perms/templates/perms/asset_permission_list.html:59
#: perms/templates/perms/asset_permission_list.html:73
+#: perms/templates/perms/remote_app_permission_list.html:18
#: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53
#: xpack/plugins/cloud/templates/cloud/account_detail.html:58
#: xpack/plugins/cloud/templates/cloud/account_list.html:14
@@ -2866,6 +3073,7 @@ msgid "Validity"
msgstr "有效"
#: perms/templates/perms/asset_permission_user.html:35
+#: perms/templates/perms/remote_app_permission_user.html:34
msgid "User list of "
msgstr "用户列表"
@@ -2878,35 +3086,95 @@ msgid "Add user group to asset permission"
msgstr "添加用户组"
#: perms/templates/perms/asset_permission_user.html:116
+#: perms/templates/perms/remote_app_permission_user.html:115
msgid "Select user groups"
msgstr "选择用户组"
-#: perms/views.py:24 perms/views.py:56 perms/views.py:71 perms/views.py:86
-#: perms/views.py:121 perms/views.py:153 templates/_nav.html:31
+#: perms/templates/perms/remote_app_permission_detail.html:74
+msgid "RemoteApp count"
+msgstr "远程应用数量"
+
+#: perms/templates/perms/remote_app_permission_remote_app.html:34
+msgid "RemoteApp list of "
+msgstr "远程应用列表"
+
+#: perms/templates/perms/remote_app_permission_remote_app.html:79
+msgid "Add RemoteApp to this permission"
+msgstr "添加远程应用"
+
+#: perms/templates/perms/remote_app_permission_remote_app.html:87
+msgid "Select RemoteApp"
+msgstr "选择远程应用"
+
+#: perms/templates/perms/remote_app_permission_user.html:79
+msgid "Add user to this permission"
+msgstr "添加用户"
+
+#: perms/templates/perms/remote_app_permission_user.html:107
+msgid "Add user group to this permission"
+msgstr "添加用户组"
+
+#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:65
+#: perms/views/asset_permission.py:80 perms/views/asset_permission.py:95
+#: perms/views/asset_permission.py:130 perms/views/asset_permission.py:162
+#: perms/views/remote_app_permission.py:33
+#: perms/views/remote_app_permission.py:48
+#: perms/views/remote_app_permission.py:63
+#: perms/views/remote_app_permission.py:76
+#: perms/views/remote_app_permission.py:102
+#: perms/views/remote_app_permission.py:138 templates/_nav.html:39
#: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Perms"
msgstr "权限管理"
-#: perms/views.py:25
+#: perms/views/asset_permission.py:34
msgid "Asset permission list"
msgstr "资产授权列表"
-#: perms/views.py:57
+#: perms/views/asset_permission.py:66
msgid "Create asset permission"
msgstr "创建权限规则"
-#: perms/views.py:72 perms/views.py:87
+#: perms/views/asset_permission.py:81
msgid "Update asset permission"
msgstr "更新资产授权"
-#: perms/views.py:122
+#: perms/views/asset_permission.py:96
+msgid "Asset permission detail"
+msgstr "资产授权详情"
+
+#: perms/views/asset_permission.py:131
msgid "Asset permission user list"
msgstr "资产授权用户列表"
-#: perms/views.py:154
+#: perms/views/asset_permission.py:163
msgid "Asset permission asset list"
msgstr "资产授权资产列表"
+#: perms/views/remote_app_permission.py:34
+msgid "RemoteApp permission list"
+msgstr "远程应用授权列表"
+
+#: perms/views/remote_app_permission.py:49
+msgid "Create RemoteApp permission"
+msgstr "创建远程应用授权规则"
+
+#: perms/views/remote_app_permission.py:64
+msgid "Update RemoteApp permission"
+msgstr "更新远程应用授权规则"
+
+#: perms/views/remote_app_permission.py:77
+msgid "RemoteApp permission detail"
+msgstr "远程应用授权详情"
+
+#: perms/views/remote_app_permission.py:103
+msgid "RemoteApp permission user list"
+msgstr "远程应用授权用户列表"
+
+#: perms/views/remote_app_permission.py:139
+msgid "RemoteApp permission RemoteApp list"
+msgstr "远程应用授权远程应用列表"
+
#: settings/api.py:26
msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查"
@@ -3376,7 +3644,7 @@ msgstr "在ou:{}中没有匹配条目"
#: settings/views.py:18 settings/views.py:44 settings/views.py:70
#: settings/views.py:99 settings/views.py:126 settings/views.py:138
-#: settings/views.py:151 templates/_nav.html:107
+#: settings/views.py:151 templates/_nav.html:118
msgid "Settings"
msgstr "系统设置"
@@ -3405,7 +3673,7 @@ msgstr "文档"
msgid "Commercial support"
msgstr "商业支持"
-#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:121
+#: templates/_header_bar.html:89 templates/_nav_user.html:26 users/forms.py:121
#: users/templates/users/_user.html:43
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40
@@ -3508,57 +3776,65 @@ msgstr "用户列表"
msgid "Command filters"
msgstr "命令过滤"
-#: templates/_nav.html:40
+#: templates/_nav.html:32
+msgid "Applications"
+msgstr "应用管理"
+
+#: templates/_nav.html:51
msgid "Sessions"
msgstr "会话管理"
-#: templates/_nav.html:43
+#: templates/_nav.html:54
msgid "Session online"
msgstr "在线会话"
-#: templates/_nav.html:44
+#: templates/_nav.html:55
msgid "Session offline"
msgstr "历史会话"
-#: templates/_nav.html:45
+#: templates/_nav.html:56
msgid "Commands"
msgstr "命令记录"
-#: templates/_nav.html:48 templates/_nav_user.html:19
+#: templates/_nav.html:59 templates/_nav_user.html:31
msgid "Web terminal"
msgstr "Web终端"
-#: templates/_nav.html:53 templates/_nav_user.html:24
+#: templates/_nav.html:64 templates/_nav_user.html:36
msgid "File manager"
msgstr "文件管理"
-#: templates/_nav.html:57 terminal/views/command.py:50
+#: templates/_nav.html:68 terminal/views/command.py:50
#: 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
msgid "Terminal"
msgstr "终端管理"
-#: templates/_nav.html:63
+#: templates/_nav.html:74
msgid "Job Center"
msgstr "作业中心"
-#: templates/_nav.html:67 templates/_nav.html:79
+#: templates/_nav.html:78 templates/_nav.html:90
msgid "Batch command"
msgstr "批量命令"
-#: templates/_nav.html:85
+#: templates/_nav.html:96
msgid "XPack"
msgstr ""
-#: templates/_nav.html:93 xpack/plugins/cloud/views.py:26
+#: templates/_nav.html:104 xpack/plugins/cloud/views.py:26
msgid "Account list"
msgstr "账户列表"
-#: templates/_nav.html:94
+#: templates/_nav.html:105
msgid "Sync instance"
msgstr "同步实例"
+#: templates/_nav_user.html:9
+msgid "My Applications"
+msgstr "我的应用"
+
#: templates/_pagination.html:59
msgid ""
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
@@ -5452,6 +5728,9 @@ msgstr "创建组织"
msgid "Update org"
msgstr "更新组织"
+#~ msgid "MFA Confirm"
+#~ msgstr "确认"
+
#~ msgid "Monitor"
#~ msgstr "监控"
@@ -5501,24 +5780,6 @@ msgstr "更新组织"
#~ msgid "Invalid private key"
#~ msgstr "ssh密钥不合法"
-#, fuzzy
-#~| msgid "CPU count"
-#~ msgid "Cpu count"
-#~ msgstr "CPU数量"
-
-#~ msgid "Login Jumpserver"
-#~ msgstr "登录 Jumpserver"
-
-#, fuzzy
-#~| msgid "Delete succeed"
-#~ msgid "Delete success!"
-#~ msgstr "删除成功"
-
-#, fuzzy
-#~| msgid "Username does not exist"
-#~ msgid "This license does not exist!"
-#~ msgstr "用户名不存在"
-
#~ msgid "Valid"
#~ msgstr "账户状态"
@@ -5540,11 +5801,6 @@ msgstr "更新组织"
#~ msgid "Date finished"
#~ msgstr "结束日期"
-#, fuzzy
-#~| msgid "Audits"
-#~ msgid "Audit"
-#~ msgstr "日志审计"
-
#~ msgid "User id"
#~ msgstr "用户"
@@ -5554,11 +5810,6 @@ msgstr "更新组织"
#~ msgid "Start"
#~ msgstr "开始"
-#, fuzzy
-#~| msgid "Update setting successfully"
-#~ msgid "Update setting successfully, please restart program"
-#~ msgstr "更新设置成功"
-
#~ msgid "User login settings"
#~ msgstr "用户登录设置"
diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py
index e90e0262c..aebfb2d33 100644
--- a/apps/perms/api/__init__.py
+++ b/apps/perms/api/__init__.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
-from .permission import *
+from .asset_permission import *
from .user_permission import *
from .user_group_permission import *
+from .remote_app_permission import *
diff --git a/apps/perms/api/permission.py b/apps/perms/api/asset_permission.py
similarity index 100%
rename from apps/perms/api/permission.py
rename to apps/perms/api/asset_permission.py
diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py
new file mode 100644
index 000000000..d75e80b8f
--- /dev/null
+++ b/apps/perms/api/remote_app_permission.py
@@ -0,0 +1,101 @@
+# coding: utf-8
+#
+
+
+from rest_framework import viewsets, generics
+from rest_framework.pagination import LimitOffsetPagination
+from rest_framework.views import Response
+
+from common.permissions import IsOrgAdmin
+
+from ..models import RemoteAppPermission
+from ..serializers import (
+ RemoteAppPermissionSerializer,
+ RemoteAppPermissionUpdateUserSerializer,
+ RemoteAppPermissionUpdateRemoteAppSerializer,
+)
+
+
+__all__ = [
+ 'RemoteAppPermissionViewSet',
+ 'RemoteAppPermissionAddUserApi', 'RemoteAppPermissionAddRemoteAppApi',
+ 'RemoteAppPermissionRemoveUserApi', 'RemoteAppPermissionRemoveRemoteAppApi',
+]
+
+
+class RemoteAppPermissionViewSet(viewsets.ModelViewSet):
+ filter_fields = ('name', )
+ search_fields = filter_fields
+ queryset = RemoteAppPermission.objects.all()
+ serializer_class = RemoteAppPermissionSerializer
+ pagination_class = LimitOffsetPagination
+ permission_classes = (IsOrgAdmin,)
+
+
+class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView):
+ permission_classes = (IsOrgAdmin,)
+ serializer_class = RemoteAppPermissionUpdateUserSerializer
+ queryset = RemoteAppPermission.objects.all()
+
+ def update(self, request, *args, **kwargs):
+ perm = self.get_object()
+ serializer = self.serializer_class(data=request.data)
+ if serializer.is_valid():
+ users = serializer.validated_data.get('users')
+ if users:
+ perm.users.add(*tuple(users))
+ return Response({"msg": "ok"})
+ else:
+ return Response({"error": serializer.errors})
+
+
+class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
+ permission_classes = (IsOrgAdmin,)
+ serializer_class = RemoteAppPermissionUpdateUserSerializer
+ queryset = RemoteAppPermission.objects.all()
+
+ def update(self, request, *args, **kwargs):
+ perm = self.get_object()
+ serializer = self.serializer_class(data=request.data)
+ if serializer.is_valid():
+ users = serializer.validated_data.get('users')
+ if users:
+ perm.users.remove(*tuple(users))
+ return Response({"msg": "ok"})
+ else:
+ return Response({"error": serializer.errors})
+
+
+class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView):
+ permission_classes = (IsOrgAdmin,)
+ serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
+ queryset = RemoteAppPermission.objects.all()
+
+ def update(self, request, *args, **kwargs):
+ perm = self.get_object()
+ serializer = self.serializer_class(data=request.data)
+ if serializer.is_valid():
+ remote_apps = serializer.validated_data.get('remote_apps')
+ if remote_apps:
+ perm.remote_apps.add(*tuple(remote_apps))
+ return Response({"msg": "ok"})
+ else:
+ return Response({"error": serializer.errors})
+
+
+class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
+ permission_classes = (IsOrgAdmin,)
+ serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
+ queryset = RemoteAppPermission.objects.all()
+
+ def update(self, request, *args, **kwargs):
+ perm = self.get_object()
+ serializer = self.serializer_class(data=request.data)
+ if serializer.is_valid():
+ remote_apps = serializer.validated_data.get('remote_apps')
+ if remote_apps:
+ perm.remote_apps.remove(*tuple(remote_apps))
+ return Response({"msg": "ok"})
+ else:
+ return Response({"error": serializer.errors})
+
diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py
index 82b71bb63..4e5c8acd9 100644
--- a/apps/perms/api/user_group_permission.py
+++ b/apps/perms/api/user_group_permission.py
@@ -10,10 +10,12 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer
from orgs.utils import set_to_root_org
from ..utils import (
- AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node
+ AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
+ RemoteAppPermissionUtil,
)
from ..hands import (
- AssetGrantedSerializer, UserGroup, Node, NodeSerializer
+ AssetGrantedSerializer, UserGroup, Node, NodeSerializer,
+ RemoteAppSerializer,
)
from .. import serializers
@@ -22,6 +24,7 @@ __all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
'UserGroupGrantedNodesWithAssetsAsTreeApi',
+ 'UserGroupGrantedRemoteAppsApi',
]
@@ -138,3 +141,20 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
for asset, system_users in assets.items():
asset.system_users_granted = system_users
return assets
+
+
+# RemoteApp permission
+
+class UserGroupGrantedRemoteAppsApi(ListAPIView):
+ permission_classes = (IsOrgAdmin, )
+ serializer_class = RemoteAppSerializer
+
+ def get_queryset(self):
+ queryset = []
+ user_group_id = self.kwargs.get('pk')
+ if not user_group_id:
+ return queryset
+ user_group = get_object_or_404(UserGroup, id=user_group_id)
+ util = RemoteAppPermissionUtil(user_group)
+ queryset = util.get_remote_apps()
+ return queryset
diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py
index 8d1a42c26..7c934340e 100644
--- a/apps/perms/api/user_permission.py
+++ b/apps/perms/api/user_permission.py
@@ -17,14 +17,15 @@ from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
- check_system_user_action
+ check_system_user_action, RemoteAppPermissionUtil,
+ construct_remote_apps_tree_root, parse_remote_app_to_tree_node,
)
from ..hands import (
- AssetGrantedSerializer, User, Asset, Node,
- SystemUser, NodeSerializer
+ User, Asset, Node, SystemUser, RemoteApp, AssetGrantedSerializer,
+ NodeSerializer, RemoteAppSerializer,
)
from .. import serializers
-from ..mixins import AssetsFilterMixin
+from ..mixins import AssetsFilterMixin, RemoteAppFilterMixin
from ..models import Action
logger = get_logger(__name__)
@@ -34,6 +35,8 @@ __all__ = [
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
+ 'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
+ 'UserGrantedRemoteAppsAsTreeApi',
]
@@ -447,3 +450,78 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
actions = [action.name for action in getattr(_su, 'actions', [])]
return Response({'actions': actions}, status=200)
+
+
+# RemoteApp permission
+
+class UserGrantedRemoteAppsApi(RemoteAppFilterMixin, ListAPIView):
+ permission_classes = (IsOrgAdminOrAppUser,)
+ serializer_class = RemoteAppSerializer
+ pagination_class = LimitOffsetPagination
+
+ def get_object(self):
+ user_id = self.kwargs.get('pk', '')
+ if user_id:
+ user = get_object_or_404(User, id=user_id)
+ else:
+ user = self.request.user
+ return user
+
+ def get_queryset(self):
+ util = RemoteAppPermissionUtil(self.get_object())
+ queryset = util.get_remote_apps()
+ queryset = list(queryset)
+ return queryset
+
+ def get_permissions(self):
+ if self.kwargs.get('pk') is None:
+ self.permission_classes = (IsValidUser,)
+ return super().get_permissions()
+
+
+class UserGrantedRemoteAppsAsTreeApi(ListAPIView):
+ serializer_class = TreeNodeSerializer
+ permission_classes = (IsOrgAdminOrAppUser,)
+
+ def get_object(self):
+ user_id = self.kwargs.get('pk', '')
+ if not user_id:
+ user = self.request.user
+ else:
+ user = get_object_or_404(User, id=user_id)
+ return user
+
+ def get_queryset(self):
+ queryset = []
+ tree_root = construct_remote_apps_tree_root()
+ queryset.append(tree_root)
+
+ util = RemoteAppPermissionUtil(self.get_object())
+ remote_apps = util.get_remote_apps()
+ for remote_app in remote_apps:
+ node = parse_remote_app_to_tree_node(tree_root, remote_app)
+ queryset.append(node)
+
+ queryset = sorted(queryset)
+ return queryset
+
+ def get_permissions(self):
+ if self.kwargs.get('pk') is None:
+ self.permission_classes = (IsValidUser,)
+ return super().get_permissions()
+
+
+class ValidateUserRemoteAppPermissionApi(APIView):
+
+ def get(self, request, *args, **kwargs):
+ user_id = request.query_params.get('user_id', '')
+ remote_app_id = request.query_params.get('remote_app_id', '')
+ user = get_object_or_404(User, id=user_id)
+ remote_app = get_object_or_404(RemoteApp, id=remote_app_id)
+
+ util = RemoteAppPermissionUtil(user)
+ remote_apps = util.get_remote_apps()
+ if remote_app not in remote_apps:
+ return Response({'msg': False}, status=403)
+
+ return Response({'msg': True}, status=200)
diff --git a/apps/perms/forms/__init__.py b/apps/perms/forms/__init__.py
new file mode 100644
index 000000000..129901afc
--- /dev/null
+++ b/apps/perms/forms/__init__.py
@@ -0,0 +1,5 @@
+# coding: utf-8
+#
+
+from .asset_permission import *
+from .remote_app_permission import *
diff --git a/apps/perms/forms.py b/apps/perms/forms/asset_permission.py
similarity index 97%
rename from apps/perms/forms.py
rename to apps/perms/forms/asset_permission.py
index 35d8a2528..81845fb77 100644
--- a/apps/perms/forms.py
+++ b/apps/perms/forms/asset_permission.py
@@ -6,9 +6,13 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
-from .models import AssetPermission
+from perms.models import AssetPermission
from assets.models import Asset
+__all__ = [
+ 'AssetPermissionForm',
+]
+
class AssetPermissionForm(OrgModelForm):
def __init__(self, *args, **kwargs):
diff --git a/apps/perms/forms/remote_app_permission.py b/apps/perms/forms/remote_app_permission.py
new file mode 100644
index 000000000..2e0cc1b66
--- /dev/null
+++ b/apps/perms/forms/remote_app_permission.py
@@ -0,0 +1,49 @@
+# coding: utf-8
+#
+
+from django.utils.translation import ugettext as _
+from django import forms
+from orgs.mixins import OrgModelForm
+from orgs.utils import current_org
+
+from ..models import RemoteAppPermission
+
+
+__all__ = [
+ 'RemoteAppPermissionCreateUpdateForm',
+]
+
+
+class RemoteAppPermissionCreateUpdateForm(OrgModelForm):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ users_field = self.fields.get('users')
+ if hasattr(users_field, 'queryset'):
+ users_field.queryset = current_org.get_org_users()
+
+ class Meta:
+ model = RemoteAppPermission
+ exclude = (
+ 'id', 'date_created', 'created_by', 'org_id'
+ )
+ widgets = {
+ 'users': forms.SelectMultiple(
+ attrs={'class': 'select2', 'data-placeholder': _('User')}
+ ),
+ 'user_groups': forms.SelectMultiple(
+ attrs={'class': 'select2', 'data-placeholder': _('User group')}
+ ),
+ 'remote_apps': forms.SelectMultiple(
+ attrs={'class': 'select2', 'data-placeholder': _('RemoteApp')}
+ )
+ }
+
+ def clean_user_groups(self):
+ users = self.cleaned_data.get('users')
+ user_groups = self.cleaned_data.get('user_groups')
+
+ if not users and not user_groups:
+ raise forms.ValidationError(
+ _("User or group at least one required")
+ )
+ return self.cleaned_data['user_groups']
diff --git a/apps/perms/hands.py b/apps/perms/hands.py
index 56a60cba7..2a208fefa 100644
--- a/apps/perms/hands.py
+++ b/apps/perms/hands.py
@@ -3,8 +3,11 @@
from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup
-from assets.models import Asset, SystemUser, Node
-from assets.serializers import AssetGrantedSerializer, NodeSerializer
+from assets.models import Asset, SystemUser, Node, RemoteApp
+from assets.serializers import (
+ AssetGrantedSerializer, NodeSerializer
+)
+from applications.serializers import RemoteAppSerializer
diff --git a/apps/perms/migrations/0005_auto_20190520_1904.py b/apps/perms/migrations/0005_auto_20190520_1904.py
new file mode 100644
index 000000000..48c579201
--- /dev/null
+++ b/apps/perms/migrations/0005_auto_20190520_1904.py
@@ -0,0 +1,45 @@
+# Generated by Django 2.1.7 on 2019-05-20 11:04
+
+import common.utils.django
+from django.conf import settings
+from django.db import migrations, models
+import django.utils.timezone
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('applications', '0001_initial'),
+ ('users', '0019_auto_20190304_1459'),
+ ('perms', '0004_assetpermission_actions'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RemoteAppPermission',
+ fields=[
+ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
+ ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
+ ('name', models.CharField(max_length=128, verbose_name='Name')),
+ ('is_active', models.BooleanField(default=True, verbose_name='Active')),
+ ('date_start', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date start')),
+ ('date_expired', models.DateTimeField(db_index=True, default=common.utils.django.date_expired_default, verbose_name='Date expired')),
+ ('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')),
+ ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
+ ('comment', models.TextField(blank=True, verbose_name='Comment')),
+ ('remote_apps', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='applications.RemoteApp', verbose_name='RemoteApp')),
+ ('user_groups', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='users.UserGroup', verbose_name='User group')),
+ ('users', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to=settings.AUTH_USER_MODEL, verbose_name='User')),
+ ],
+ options={
+ 'verbose_name': 'RemoteApp permission',
+ 'ordering': ('name',),
+ },
+ ),
+ migrations.AlterUniqueTogether(
+ name='remoteapppermission',
+ unique_together={('org_id', 'name')},
+ ),
+ ]
diff --git a/apps/perms/mixins.py b/apps/perms/mixins.py
index e41c0529b..3adaa6e5b 100644
--- a/apps/perms/mixins.py
+++ b/apps/perms/mixins.py
@@ -2,6 +2,11 @@
#
+__all__ = [
+ 'AssetsFilterMixin', 'RemoteAppFilterMixin',
+]
+
+
class AssetsFilterMixin(object):
"""
对资产进行过滤(查询,排序)
@@ -34,3 +39,38 @@ class AssetsFilterMixin(object):
queryset = sort_assets(queryset, order_by=order_by, reverse=reverse)
return queryset
+
+
+class RemoteAppFilterMixin(object):
+ """
+ 对RemoteApp进行过滤(查询,排序)
+ """
+
+ def filter_queryset(self, queryset):
+ queryset = self.search_remote_apps(queryset)
+ queryset = self.sort_remote_apps(queryset)
+ return queryset
+
+ def search_remote_apps(self, queryset):
+ value = self.request.query_params.get('search')
+ if not value:
+ return queryset
+ queryset = [
+ remote_app for remote_app in queryset if value in remote_app.name
+ ]
+ return queryset
+
+ def sort_remote_apps(self, queryset):
+ order_by = self.request.query_params.get('order')
+ if not order_by:
+ order_by = 'name'
+ if order_by.startswith('-'):
+ order_by = order_by.lstrip('-')
+ reverse = True
+ else:
+ reverse = False
+
+ queryset = sorted(
+ queryset, key=lambda x: getattr(x, order_by), reverse=reverse
+ )
+ return queryset
diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py
new file mode 100644
index 000000000..129901afc
--- /dev/null
+++ b/apps/perms/models/__init__.py
@@ -0,0 +1,5 @@
+# coding: utf-8
+#
+
+from .asset_permission import *
+from .remote_app_permission import *
diff --git a/apps/perms/models.py b/apps/perms/models/asset_permission.py
similarity index 97%
rename from apps/perms/models.py
rename to apps/perms/models/asset_permission.py
index c524c4f58..ca917d69d 100644
--- a/apps/perms/models.py
+++ b/apps/perms/models/asset_permission.py
@@ -7,7 +7,12 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin, OrgManager
-from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
+from perms.const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
+
+
+__all__ = [
+ 'Action', 'AssetPermission', 'NodePermission',
+]
class Action(models.Model):
diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py
new file mode 100644
index 000000000..7b29c8d20
--- /dev/null
+++ b/apps/perms/models/remote_app_permission.py
@@ -0,0 +1,75 @@
+# coding: utf-8
+#
+
+import uuid
+from django.db import models
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
+
+from orgs.mixins import OrgModelMixin, OrgManager
+from common.utils import date_expired_default, set_or_append_attr_bulk
+
+__all__ = [
+ 'RemoteAppPermission',
+]
+
+
+class RemoteAppPermissionQuerySet(models.QuerySet):
+ def active(self):
+ return self.filter(is_active=True)
+
+ def valid(self):
+ return self.active().filter(date_start__lt=timezone.now())\
+ .filter(date_expired__gt=timezone.now())
+
+
+class RemoteAppPermissionManager(OrgManager):
+ def valid(self):
+ return self.get_queryset().valid()
+
+
+class RemoteAppPermission(OrgModelMixin):
+ id = models.UUIDField(default=uuid.uuid4, primary_key=True)
+ name = models.CharField(max_length=128, verbose_name=_('Name'))
+ users = models.ManyToManyField('users.User', related_name='remote_app_permissions', blank=True, verbose_name=_("User"))
+ user_groups = models.ManyToManyField('users.UserGroup', related_name='remote_app_permissions', blank=True, verbose_name=_("User group"))
+ remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='remote_app_permissions', blank=True, verbose_name=_("RemoteApp"))
+ is_active = models.BooleanField(default=True, verbose_name=_('Active'))
+ date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
+ date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))
+ created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
+ date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
+ comment = models.TextField(verbose_name=_('Comment'), blank=True)
+
+ objects = RemoteAppPermissionManager.from_queryset(RemoteAppPermissionQuerySet)()
+
+ class Meta:
+ unique_together = [('org_id', 'name')]
+ verbose_name = _('RemoteApp permission')
+ ordering = ('name',)
+
+ def __str__(self):
+ return self.name
+
+ @property
+ def is_expired(self):
+ if self.date_expired > timezone.now() > self.date_start:
+ return False
+ return True
+
+ @property
+ def is_valid(self):
+ if not self.is_expired and self.is_active:
+ return True
+ return False
+
+ def get_all_users(self):
+ users = set(self.users.all())
+ for group in self.user_groups.all():
+ _users = group.users.all()
+ set_or_append_attr_bulk(_users, 'inherit', group.name)
+ users.update(set(_users))
+ return users
+
+ def get_all_remote_apps(self):
+ return set(self.remote_apps.all())
diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py
new file mode 100644
index 000000000..129901afc
--- /dev/null
+++ b/apps/perms/serializers/__init__.py
@@ -0,0 +1,5 @@
+# coding: utf-8
+#
+
+from .asset_permission import *
+from .remote_app_permission import *
diff --git a/apps/perms/serializers.py b/apps/perms/serializers/asset_permission.py
similarity index 97%
rename from apps/perms/serializers.py
rename to apps/perms/serializers/asset_permission.py
index 01c5a077a..a1eec6710 100644
--- a/apps/perms/serializers.py
+++ b/apps/perms/serializers/asset_permission.py
@@ -4,7 +4,7 @@
from rest_framework import serializers
from common.fields import StringManyToManyField
-from .models import AssetPermission, Action
+from perms.models import AssetPermission, Action
from assets.models import Node, Asset, SystemUser
from assets.serializers import AssetGrantedSerializer
@@ -13,7 +13,7 @@ __all__ = [
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer',
'GrantedAssetSerializer', 'GrantedSystemUserSerializer',
- 'ActionSerializer',
+ 'ActionSerializer', 'NodeGrantedSerializer',
]
diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py
new file mode 100644
index 000000000..bb95d910c
--- /dev/null
+++ b/apps/perms/serializers/remote_app_permission.py
@@ -0,0 +1,36 @@
+# coding: utf-8
+#
+
+from rest_framework import serializers
+
+from ..models import RemoteAppPermission
+
+
+__all__ = [
+ 'RemoteAppPermissionSerializer',
+ 'RemoteAppPermissionUpdateUserSerializer',
+ 'RemoteAppPermissionUpdateRemoteAppSerializer',
+]
+
+
+class RemoteAppPermissionSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = RemoteAppPermission
+ fields = [
+ 'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment',
+ 'is_active', 'date_start', 'date_expired', 'is_valid',
+ 'created_by', 'date_created', 'org_id'
+ ]
+ read_only_fields = ['created_by', 'date_created']
+
+
+class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = RemoteAppPermission
+ fields = ['id', 'users']
+
+
+class RemoteAppPermissionUpdateRemoteAppSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = RemoteAppPermission
+ fields = ['id', 'remote_apps']
diff --git a/apps/perms/templates/perms/remote_app_permission_create_update.html b/apps/perms/templates/perms/remote_app_permission_create_update.html
new file mode 100644
index 000000000..3d6154991
--- /dev/null
+++ b/apps/perms/templates/perms/remote_app_permission_create_update.html
@@ -0,0 +1,120 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% load static %}
+{% load bootstrap3 %}
+{% block custom_head_css_js %}
+
+
+
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
+{% block custom_foot_js %}
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/perms/templates/perms/remote_app_permission_detail.html b/apps/perms/templates/perms/remote_app_permission_detail.html
new file mode 100644
index 000000000..98bc6633e
--- /dev/null
+++ b/apps/perms/templates/perms/remote_app_permission_detail.html
@@ -0,0 +1,169 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block custom_head_css_js %}
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% trans 'Name' %}:
+ {{ object.name }}
+
+
+ {% trans 'User count' %}:
+ {{ object.users.count }}
+
+
+ {% trans 'User group count' %}:
+ {{ object.user_groups.count }}
+
+
+ {% trans 'RemoteApp count' %}:
+ {{ object.remote_apps.count }}
+
+
+ {% trans 'Date start' %}:
+ {{ object.date_start }}
+
+
+ {% trans 'Date expired' %}:
+ {{ object.date_expired }}
+
+
+ {% trans 'Date created' %}:
+ {{ object.date_created }}
+
+
+ {% trans 'Created by' %}:
+ {{ object.created_by }}
+
+
+ {% trans 'Comment' %}:
+ {{ object.comment }}
+
+
+
+
+
+
+
+
+
+
+ {% trans 'Quick update' %}
+
+
+
+
+
+ {% trans 'Active' %} :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/perms/templates/perms/remote_app_permission_list.html b/apps/perms/templates/perms/remote_app_permission_list.html
new file mode 100644
index 000000000..8f1681781
--- /dev/null
+++ b/apps/perms/templates/perms/remote_app_permission_list.html
@@ -0,0 +1,93 @@
+{% extends '_base_list.html' %}
+{% load i18n static %}
+{% block table_search %}{% endblock %}
+{% block table_container %}
+
+
+{% endblock %}
+{% block content_bottom_left %}{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
diff --git a/apps/perms/templates/perms/remote_app_permission_remote_app.html b/apps/perms/templates/perms/remote_app_permission_remote_app.html
new file mode 100644
index 000000000..63d395941
--- /dev/null
+++ b/apps/perms/templates/perms/remote_app_permission_remote_app.html
@@ -0,0 +1,164 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block custom_head_css_js %}
+
+
+{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+
{% trans 'RemoteApp list of ' %} {{ remote_app_permission.name }}
+
+
+
+
+
+
+ {% trans 'Name' %}
+ {% trans 'Type' %}
+
+
+
+
+ {% for remote_app in object_list %}
+
+ {{ remote_app.name }}
+ {{ remote_app.get_type_display }}
+
+
+
+
+ {% endfor %}
+
+
+
+ {% include '_pagination.html' %}
+
+
+
+
+
+
+
+ {% trans 'Add RemoteApp to this permission' %}
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/perms/templates/perms/remote_app_permission_user.html b/apps/perms/templates/perms/remote_app_permission_user.html
new file mode 100644
index 000000000..7433327c5
--- /dev/null
+++ b/apps/perms/templates/perms/remote_app_permission_user.html
@@ -0,0 +1,256 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block custom_head_css_js %}
+
+
+{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+
{% trans 'User list of ' %} {{ remote_app_permission.name }}
+
+
+
+
+
+
+ {% trans 'Name' %}
+ {% trans 'Username' %}
+
+
+
+
+ {% for user in object_list %}
+
+ {{ user.name }}
+ {{ user.username }}
+
+
+
+
+ {% endfor %}
+
+
+
+ {% include '_pagination.html' %}
+
+
+
+
+
+
+
+ {% trans 'Add user to this permission' %}
+
+
+
+
+
+
+ {% trans 'Add user group to this permission' %}
+
+
+
+
+
+
+ {% for user_group in remote_app_permission.user_groups.all %}
+
+ {{ user_group }}
+
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py
index 5894d3835..48f2ace61 100644
--- a/apps/perms/urls/api_urls.py
+++ b/apps/perms/urls/api_urls.py
@@ -9,8 +9,9 @@ app_name = 'perms'
router = routers.DefaultRouter()
router.register('actions', api.ActionViewSet, 'action')
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
+router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission')
-urlpatterns = [
+asset_permission_urlpatterns = [
# 查询某个用户授权的资产和资产组
path('user//assets/',
api.UserGrantedAssetsApi.as_view(), name='user-assets'),
@@ -35,7 +36,6 @@ urlpatterns = [
path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(),
name='my-nodes-assets-as-tree'),
-
# 查询某个用户组授权的资产和资产组
path('user-group//assets/',
api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
@@ -72,5 +72,48 @@ urlpatterns = [
name='get-user-asset-permission-actions'),
]
+
+remote_app_permission_urlpatterns = [
+ # 查询用户授权的RemoteApp
+ path('user//remote-apps/',
+ api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'),
+ path('user/remote-apps/',
+ api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'),
+
+ # 获取用户授权的RemoteApp树
+ path('user//remote-apps/tree/',
+ api.UserGrantedRemoteAppsAsTreeApi.as_view(),
+ name='user-remote-apps-as-tree'),
+ path('user/remote-apps/tree/',
+ api.UserGrantedRemoteAppsAsTreeApi.as_view(),
+ name='my-remote-apps-as-tree'),
+
+ # 查询用户组授权的RemoteApp
+ path('user-group//remote-apps/',
+ api.UserGroupGrantedRemoteAppsApi.as_view(),
+ name='user-group-remote-apps'),
+
+ # 校验用户对RemoteApp的权限
+ path('remote-app-permission/user/validate/',
+ api.ValidateUserRemoteAppPermissionApi.as_view(),
+ name='validate-user-remote-app-permission'),
+
+ # 用户和RemoteApp变更
+ path('remote-app-permissions//user/add/',
+ api.RemoteAppPermissionAddUserApi.as_view(),
+ name='remote-app-permission-add-user'),
+ path('remote-app-permissions//user/remove/',
+ api.RemoteAppPermissionRemoveUserApi.as_view(),
+ name='remote-app-permission-remove-user'),
+ path('remote-app-permissions//remote-app/remove/',
+ api.RemoteAppPermissionRemoveRemoteAppApi.as_view(),
+ name='remote-app-permission-remove-remote-app'),
+ path('remote-app-permissions//remote-app/add/',
+ api.RemoteAppPermissionAddRemoteAppApi.as_view(),
+ name='remote-app-permission-add-remote-app'),
+]
+
+urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns
+
urlpatterns += router.urls
diff --git a/apps/perms/urls/views_urls.py b/apps/perms/urls/views_urls.py
index 8b8348d28..964025db3 100644
--- a/apps/perms/urls/views_urls.py
+++ b/apps/perms/urls/views_urls.py
@@ -7,6 +7,7 @@ from .. import views
app_name = 'perms'
urlpatterns = [
+ # asset-permission
path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
path('asset-permission/create/', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
path('asset-permission//update/', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
@@ -14,4 +15,12 @@ urlpatterns = [
path('asset-permission//delete/', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
path('asset-permission//user/', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
path('asset-permission//asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
+
+ # remote-app-permission
+ path('remote-app-permission/', views.RemoteAppPermissionListView.as_view(), name='remote-app-permission-list'),
+ path('remote-app-permission/create/', views.RemoteAppPermissionCreateView.as_view(), name='remote-app-permission-create'),
+ path('remote-app-permission//update/', views.RemoteAppPermissionUpdateView.as_view(), name='remote-app-permission-update'),
+ path('remote-app-permission//', views.RemoteAppPermissionDetailView.as_view(), name='remote-app-permission-detail'),
+ path('remote-app-permission//user/', views.RemoteAppPermissionUserView.as_view(), name='remote-app-permission-user-list'),
+ path('remote-app-permission//remote-app/', views.RemoteAppPermissionRemoteAppView.as_view(), name='remote-app-permission-remote-app-list'),
]
diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py
new file mode 100644
index 000000000..129901afc
--- /dev/null
+++ b/apps/perms/utils/__init__.py
@@ -0,0 +1,5 @@
+# coding: utf-8
+#
+
+from .asset_permission import *
+from .remote_app_permission import *
diff --git a/apps/perms/utils.py b/apps/perms/utils/asset_permission.py
similarity index 98%
rename from apps/perms/utils.py
rename to apps/perms/utils/asset_permission.py
index bd5094def..cacad1718 100644
--- a/apps/perms/utils.py
+++ b/apps/perms/utils/asset_permission.py
@@ -12,12 +12,19 @@ from django.conf import settings
from common.utils import get_logger
from common.tree import TreeNode
-from .models import AssetPermission, Action
-from .hands import Node
+from perms.models import AssetPermission, Action
+from perms.hands import Node
logger = get_logger(__file__)
+__all__ = [
+ 'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets',
+ 'parse_asset_to_tree_node', 'parse_node_to_tree_node',
+ 'check_system_user_action',
+]
+
+
class GenerateTree:
def __init__(self):
"""
@@ -378,7 +385,7 @@ def sort_assets(assets, order_by='hostname', reverse=False):
def parse_node_to_tree_node(node):
- from . import serializers
+ from .. import serializers
name = '{} ({})'.format(node.value, node.assets_amount)
node_serializer = serializers.GrantedNodeSerializer(node)
data = {
@@ -444,11 +451,6 @@ def parse_asset_to_tree_node(node, asset, system_users):
return tree_node
-#
-# actions
-#
-
-
def check_system_user_action(system_user, action):
"""
:param system_user: SystemUser object (包含动态属性: actions)
diff --git a/apps/perms/utils/remote_app_permission.py b/apps/perms/utils/remote_app_permission.py
new file mode 100644
index 000000000..0f67e32dc
--- /dev/null
+++ b/apps/perms/utils/remote_app_permission.py
@@ -0,0 +1,81 @@
+# coding: utf-8
+#
+
+from django.db.models import Q
+
+from common.tree import TreeNode
+
+from ..models import RemoteAppPermission
+
+
+__all__ = [
+ 'RemoteAppPermissionUtil',
+ 'construct_remote_apps_tree_root',
+ 'parse_remote_app_to_tree_node',
+]
+
+
+def get_user_remote_app_permissions(user, include_group=True):
+ if include_group:
+ groups = user.groups.all()
+ arg = Q(users=user) | Q(user_groups__in=groups)
+ else:
+ arg = Q(users=user)
+ return RemoteAppPermission.objects.all().valid().filter(arg)
+
+
+def get_user_group_remote_app_permissions(user_group):
+ return RemoteAppPermission.objects.all().valid().filter(
+ user_groups=user_group
+ )
+
+
+class RemoteAppPermissionUtil:
+ get_permissions_map = {
+ "User": get_user_remote_app_permissions,
+ "UserGroup": get_user_group_remote_app_permissions,
+ }
+
+ def __init__(self, obj):
+ self.object = obj
+
+ @property
+ def permissions(self):
+ obj_class = self.object.__class__.__name__
+ func = self.get_permissions_map[obj_class]
+ _permissions = func(self.object)
+ return _permissions
+
+ def get_remote_apps(self):
+ remote_apps = set()
+ for perm in self.permissions:
+ remote_apps.update(list(perm.remote_apps.all()))
+ return remote_apps
+
+
+def construct_remote_apps_tree_root():
+ tree_root = {
+ 'id': 'ID_REMOTE_APP_ROOT',
+ 'name': 'RemoteApp',
+ 'title': 'RemoteApp',
+ 'pId': '',
+ 'open': False,
+ 'isParent': True,
+ 'iconSkin': '',
+ 'meta': {'type': 'remote_app'}
+ }
+ return TreeNode(**tree_root)
+
+
+def parse_remote_app_to_tree_node(parent, remote_app):
+ tree_node = {
+ 'id': remote_app.id,
+ 'name': remote_app.name,
+ 'title': remote_app.name,
+ 'pId': parent.id,
+ 'open': False,
+ 'isParent': False,
+ 'iconSkin': 'file',
+ 'meta': {'type': 'remote_app'}
+ }
+ return TreeNode(**tree_node)
diff --git a/apps/perms/views/__init__.py b/apps/perms/views/__init__.py
new file mode 100644
index 000000000..129901afc
--- /dev/null
+++ b/apps/perms/views/__init__.py
@@ -0,0 +1,5 @@
+# coding: utf-8
+#
+
+from .asset_permission import *
+from .remote_app_permission import *
diff --git a/apps/perms/views.py b/apps/perms/views/asset_permission.py
similarity index 91%
rename from apps/perms/views.py
rename to apps/perms/views/asset_permission.py
index 0e02b38a7..fc16b70a9 100644
--- a/apps/perms/views.py
+++ b/apps/perms/views/asset_permission.py
@@ -10,10 +10,19 @@ from django.conf import settings
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
-from .hands import Node, Asset, SystemUser, User, UserGroup
-from .models import AssetPermission, Action
-from .forms import AssetPermissionForm
-from .const import PERMS_ACTION_NAME_ALL
+from perms.hands import Node, Asset, SystemUser, User, UserGroup
+from perms.models import AssetPermission, Action
+from perms.forms import AssetPermissionForm
+from perms.const import PERMS_ACTION_NAME_ALL
+
+
+__all__ = [
+ 'AssetPermissionListView', 'AssetPermissionCreateView',
+ 'AssetPermissionUpdateView', 'AssetPermissionDetailView',
+ 'AssetPermissionDeleteView', 'AssetPermissionUserView',
+ 'AssetPermissionAssetView',
+
+]
class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
@@ -84,7 +93,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
- 'action': _('Update asset permission'),
+ 'action': _('Asset permission detail'),
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py
new file mode 100644
index 000000000..f4da75ffe
--- /dev/null
+++ b/apps/perms/views/remote_app_permission.py
@@ -0,0 +1,144 @@
+# coding: utf-8
+#
+
+from django.utils.translation import ugettext as _
+from django.urls import reverse_lazy
+from django.views.generic import (
+ TemplateView, CreateView, UpdateView, DetailView, ListView
+)
+from django.views.generic.edit import SingleObjectMixin
+from django.conf import settings
+
+from common.permissions import AdminUserRequiredMixin
+from orgs.utils import current_org
+from users.models import UserGroup
+from assets.models import RemoteApp
+
+from ..models import RemoteAppPermission
+from ..forms import RemoteAppPermissionCreateUpdateForm
+
+
+__all__ = [
+ 'RemoteAppPermissionListView', 'RemoteAppPermissionCreateView',
+ 'RemoteAppPermissionUpdateView', 'RemoteAppPermissionDetailView',
+ 'RemoteAppPermissionUserView', 'RemoteAppPermissionRemoteAppView'
+]
+
+
+class RemoteAppPermissionListView(AdminUserRequiredMixin, TemplateView):
+ template_name = 'perms/remote_app_permission_list.html'
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Perms'),
+ 'action': _('RemoteApp permission list'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+
+class RemoteAppPermissionCreateView(AdminUserRequiredMixin, CreateView):
+ template_name = 'perms/remote_app_permission_create_update.html'
+ model = RemoteAppPermission
+ form_class = RemoteAppPermissionCreateUpdateForm
+ success_url = reverse_lazy('perms:remote-app-permission-list')
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Perms'),
+ 'action': _('Create RemoteApp permission'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+
+class RemoteAppPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
+ template_name = 'perms/remote_app_permission_create_update.html'
+ model = RemoteAppPermission
+ form_class = RemoteAppPermissionCreateUpdateForm
+ success_url = reverse_lazy('perms:remote-app-permission-list')
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Perms'),
+ 'action': _('Update RemoteApp permission')
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+
+class RemoteAppPermissionDetailView(AdminUserRequiredMixin, DetailView):
+ template_name = 'perms/remote_app_permission_detail.html'
+ model = RemoteAppPermission
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Perms'),
+ 'action': _('RemoteApp permission detail'),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+
+class RemoteAppPermissionUserView(AdminUserRequiredMixin,
+ SingleObjectMixin,
+ ListView):
+ template_name = 'perms/remote_app_permission_user.html'
+ context_object_name = 'remote_app_permission'
+ paginate_by = settings.DISPLAY_PER_PAGE
+ object = None
+
+ def get(self, request, *args, **kwargs):
+ self.object = self.get_object(
+ queryset=RemoteAppPermission.objects.all())
+ return super().get(request, *args, **kwargs)
+
+ def get_queryset(self):
+ queryset = list(self.object.get_all_users())
+ return queryset
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Perms'),
+ 'action': _('RemoteApp permission user list'),
+ 'users_remain': current_org.get_org_users().exclude(
+ remote_app_permissions=self.object
+ ),
+ 'user_groups_remain': UserGroup.objects.exclude(
+ remote_app_permissions=self.object
+ )
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+
+class RemoteAppPermissionRemoteAppView(AdminUserRequiredMixin,
+ SingleObjectMixin,
+ ListView):
+ template_name = 'perms/remote_app_permission_remote_app.html'
+ context_object_name = 'remote_app_permission'
+ paginate_by = settings.DISPLAY_PER_PAGE
+ object = None
+
+ def get(self, request, *args, **kwargs):
+ self.object = self.get_object(
+ queryset=RemoteAppPermission.objects.all()
+ )
+ return super().get(request, *args, **kwargs)
+
+ def get_queryset(self):
+ queryset = list(self.object.get_all_remote_apps())
+ return queryset
+
+ def get_context_data(self, **kwargs):
+ remote_app_granted = self.get_queryset()
+ remote_app_remain = RemoteApp.objects.exclude(
+ id__in=[a.id for a in remote_app_granted])
+ context = {
+ 'app': _('Perms'),
+ 'action': _('RemoteApp permission RemoteApp list'),
+ 'remote_app_remain': remote_app_remain
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html
index 5c3124f8e..c24a7acb4 100644
--- a/apps/templates/_nav.html
+++ b/apps/templates/_nav.html
@@ -27,12 +27,23 @@
{% trans 'Command filters' %}
+
+
+ {% trans 'Applications' %}
+
+
+
{% trans 'Perms' %}
diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html
index 151ed124e..5412dc37d 100644
--- a/apps/templates/_nav_user.html
+++ b/apps/templates/_nav_user.html
@@ -4,6 +4,18 @@
{% trans 'My assets' %}
+
+
+ {% trans 'My Applications' %}
+
+
+
{% trans 'Command execution' %}