mirror of https://github.com/jumpserver/jumpserver
commit
d46f5858f8
|
@ -6,7 +6,8 @@ RUN useradd jumpserver
|
||||||
|
|
||||||
COPY ./requirements /tmp/requirements
|
COPY ./requirements /tmp/requirements
|
||||||
|
|
||||||
RUN yum -y install epel-release && rpm -ivh https://repo.mysql.com/mysql57-community-release-el6.rpm
|
RUN yum -y install epel-release && \
|
||||||
|
echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
|
||||||
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
|
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
|
||||||
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && \
|
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && \
|
||||||
pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt
|
pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
|
@ -0,0 +1 @@
|
||||||
|
from .remote_app import *
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationsConfig(AppConfig):
|
||||||
|
name = 'applications'
|
|
@ -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, 'vSphere 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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
from .remote_app import *
|
|
@ -0,0 +1,132 @@
|
||||||
|
# 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
|
||||||
|
from .. import const
|
||||||
|
|
||||||
|
|
||||||
|
__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')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
def _clean_params(self):
|
||||||
|
app_type = self.data.get('type')
|
||||||
|
fields = const.REMOTE_APP_TYPE_MAP_FIELDS.get(app_type, [])
|
||||||
|
params = {}
|
||||||
|
for field in fields:
|
||||||
|
name = field['name']
|
||||||
|
value = self.cleaned_data[name]
|
||||||
|
params.update({name: value})
|
||||||
|
return params
|
||||||
|
|
||||||
|
def _save_params(self, instance):
|
||||||
|
params = self._clean_params()
|
||||||
|
instance.params = params
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
instance = super().save(commit=commit)
|
||||||
|
instance = self._save_params(instance)
|
||||||
|
return instance
|
|
@ -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
|
|
@ -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', 'vSphere 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')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
from .remote_app import *
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
from .remote_app import *
|
|
@ -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
|
|
@ -0,0 +1,123 @@
|
||||||
|
{% extends '_base_create_update.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
<form id="appForm" method="post" class="form-horizontal">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.asset layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.system_user layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.type layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.path layout="horizontal" %}
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
||||||
|
{# chrome #}
|
||||||
|
<div class="chrome-fields">
|
||||||
|
{% bootstrap_field form.chrome_target layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.chrome_username layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.chrome_password layout="horizontal" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# mysql workbench #}
|
||||||
|
<div class="mysql_workbench-fields">
|
||||||
|
{% bootstrap_field form.mysql_workbench_ip layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.mysql_workbench_name layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.mysql_workbench_username layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.mysql_workbench_password layout="horizontal" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# vmware #}
|
||||||
|
<div class="vmware_client-fields">
|
||||||
|
{% bootstrap_field form.vmware_target layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.vmware_username layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.vmware_password layout="horizontal" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# custom #}
|
||||||
|
<div class="custom-fields">
|
||||||
|
{% bootstrap_field form.custom_cmdline layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.custom_target layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.custom_username layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.custom_password layout="horizontal" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% bootstrap_field form.comment layout="horizontal" %}
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
|
|
||||||
|
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
var app_type_id = '#' + '{{ form.type.id_for_label }}';
|
||||||
|
var app_path_id = '#' + '{{ form.path.id_for_label }}';
|
||||||
|
var all_type_fields = [
|
||||||
|
'.chrome-fields',
|
||||||
|
'.mysql_workbench-fields',
|
||||||
|
'.vmware_client-fields',
|
||||||
|
'.custom-fields'
|
||||||
|
];
|
||||||
|
var app_type_map_default_fields_value = {
|
||||||
|
'chrome': {
|
||||||
|
'app_path': 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
|
||||||
|
},
|
||||||
|
'mysql_workbench': {
|
||||||
|
'app_path': 'C:\\Program Files\\MySQL\\MySQL Workbench 8.0 CE\\MySQLWorkbench.exe'
|
||||||
|
},
|
||||||
|
'vmware_client': {
|
||||||
|
'app_path': 'C:\\Program Files (x86)\\VMware\\Infrastructure\\Virtual Infrastructure Client\\Launcher\\VpxClient.exe'
|
||||||
|
},
|
||||||
|
'custom': {'app_path': ''}
|
||||||
|
};
|
||||||
|
function getAppType(){
|
||||||
|
return $(app_type_id+ " option:selected").val();
|
||||||
|
}
|
||||||
|
function initialDefaultValue(){
|
||||||
|
var app_type = getAppType();
|
||||||
|
var app_path = $(app_path_id).val();
|
||||||
|
if(app_path){
|
||||||
|
app_type_map_default_fields_value[app_type]['app_path'] = app_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function setDefaultValue(){
|
||||||
|
// 设置类型相关字段的默认值
|
||||||
|
var app_type = getAppType();
|
||||||
|
var app_path = app_type_map_default_fields_value[app_type]['app_path'];
|
||||||
|
$(app_path_id).val(app_path)
|
||||||
|
}
|
||||||
|
function hiddenFields(){
|
||||||
|
var app_type = getAppType();
|
||||||
|
$.each(all_type_fields, function(index, value){
|
||||||
|
$(value).addClass('hidden')
|
||||||
|
});
|
||||||
|
$('.' + app_type + '-fields').removeClass('hidden');
|
||||||
|
}
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.select2').select2({
|
||||||
|
closeOnSelect: true
|
||||||
|
});
|
||||||
|
initialDefaultValue();
|
||||||
|
hiddenFields();
|
||||||
|
setDefaultValue();
|
||||||
|
})
|
||||||
|
.on('change', app_type_id, function(){
|
||||||
|
hiddenFields();
|
||||||
|
setDefaultValue();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,109 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block custom_head_css_js %}
|
||||||
|
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||||
|
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="ibox float-e-margins">
|
||||||
|
<div class="panel-options">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="active">
|
||||||
|
<a href="{% url 'applications:remote-app-detail' pk=remote_app.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||||
|
</li>
|
||||||
|
<li class="pull-right">
|
||||||
|
<a class="btn btn-outline btn-default" href="{% url 'applications:remote-app-update' pk=remote_app.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||||
|
</li>
|
||||||
|
<li class="pull-right">
|
||||||
|
<a class="btn btn-outline btn-danger btn-delete-application">
|
||||||
|
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="col-sm-8" style="padding-left: 0;">
|
||||||
|
<div class="ibox float-e-margins">
|
||||||
|
<div class="ibox-title">
|
||||||
|
<span class="label"><b>{{ remote_app.name }}</b></span>
|
||||||
|
<div class="ibox-tools">
|
||||||
|
<a class="collapse-link">
|
||||||
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
|
<i class="fa fa-wrench"></i>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-user">
|
||||||
|
</ul>
|
||||||
|
<a class="close-link">
|
||||||
|
<i class="fa fa-times"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ibox-content">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr class="no-borders-tr">
|
||||||
|
<td>{% trans 'Name' %}:</td>
|
||||||
|
<td><b>{{ remote_app.name }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Asset' %}:</td>
|
||||||
|
<td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'System user' %}:</td>
|
||||||
|
<td><b><a href="{% url 'assets:system-user-detail' pk=remote_app.system_user.id %}">{{ remote_app.system_user.name }}</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'App type' %}:</td>
|
||||||
|
<td><b>{{ remote_app.get_type_display }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'App path' %}:</td>
|
||||||
|
<td><b>{{ remote_app.path }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Date created' %}:</td>
|
||||||
|
<td><b>{{ remote_app.date_created }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Created by' %}:</td>
|
||||||
|
<td><b>{{ remote_app.created_by }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Comment' %}:</td>
|
||||||
|
<td><b>{{ remote_app.comment }}</b></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
jumpserver.nodes_selected = {};
|
||||||
|
$(document).ready(function () {
|
||||||
|
})
|
||||||
|
.on('click', '.btn-delete-application', function () {
|
||||||
|
var $this = $(this);
|
||||||
|
var name = "{{ remote_app.name }}";
|
||||||
|
var rid = "{{ remote_app.id }}";
|
||||||
|
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
|
||||||
|
var redirect_url = "{% url 'applications:remote-app-list' %}";
|
||||||
|
objectDelete($this, name, the_url, redirect_url);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,90 @@
|
||||||
|
{% extends '_base_list.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
{% block help_message %}
|
||||||
|
<div class="alert alert-info 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' %}
|
||||||
|
<b><a href="https://github.com/jumpserver/Jmservisor/releases" target="view_window" >{% trans 'Download application loader' %}</a></b>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block table_search %}{% endblock %}
|
||||||
|
{% block table_container %}
|
||||||
|
<div class="uc pull-left m-r-5">
|
||||||
|
<a href="{% url 'applications:remote-app-create' %}" class="btn btn-sm btn-primary"> {% trans "Create RemoteApp" %} </a>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||||
|
</th>
|
||||||
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
|
<th class="text-center">{% trans 'App type' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Asset' %}</th>
|
||||||
|
<th class="text-center">{% trans 'System user' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Comment' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content_bottom_left %}{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
function initTable() {
|
||||||
|
var options = {
|
||||||
|
ele: $('#remote_app_list_table'),
|
||||||
|
columnDefs: [
|
||||||
|
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||||
|
cellData = htmlEscape(cellData);
|
||||||
|
{% url 'applications:remote-app-detail' pk=DEFAULT_PK as the_url %}
|
||||||
|
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||||
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
|
}},
|
||||||
|
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||||
|
var hostname = htmlEscape(cellData.hostname);
|
||||||
|
var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>';
|
||||||
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
|
||||||
|
}},
|
||||||
|
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||||
|
var name = htmlEscape(cellData.name);
|
||||||
|
var detail_btn = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + name + '</a>';
|
||||||
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
|
||||||
|
}},
|
||||||
|
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||||
|
var update_btn = '<a href="{% url "applications:remote-app-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||||
|
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
|
$(td).html(update_btn + del_btn)
|
||||||
|
}}
|
||||||
|
],
|
||||||
|
ajax_url: '{% url "api-applications:remote-app-list" %}',
|
||||||
|
columns: [
|
||||||
|
{data: "id"},
|
||||||
|
{data: "name" },
|
||||||
|
{data: "get_type_display", orderable: false},
|
||||||
|
{data: "asset_info", orderable: false},
|
||||||
|
{data: "system_user_info", orderable: false},
|
||||||
|
{data: "comment"},
|
||||||
|
{data: "id", orderable: false}
|
||||||
|
],
|
||||||
|
op_html: $('#actions').html()
|
||||||
|
};
|
||||||
|
jumpserver.initServerSideDataTable(options);
|
||||||
|
}
|
||||||
|
$(document).ready(function(){
|
||||||
|
initTable();
|
||||||
|
})
|
||||||
|
.on('click', '.btn-delete', function () {
|
||||||
|
var $this = $(this);
|
||||||
|
var $data_table = $('#remote_app_list_table').DataTable();
|
||||||
|
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||||
|
var rid = $this.data('rid');
|
||||||
|
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
|
||||||
|
objectDelete($this, name, the_url);
|
||||||
|
setTimeout( function () {
|
||||||
|
$data_table.ajax.reload();
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,79 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block custom_head_css_js %}
|
||||||
|
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="mail-box-header">
|
||||||
|
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||||
|
</th>
|
||||||
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
|
<th class="text-center">{% trans 'App type' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Asset' %}</th>
|
||||||
|
<th class="text-center">{% trans 'System user' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Comment' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
var inited = false;
|
||||||
|
var remote_app_table, url;
|
||||||
|
|
||||||
|
function initTable() {
|
||||||
|
if (inited){
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
inited = true;
|
||||||
|
}
|
||||||
|
url = '{% url "api-perms:my-remote-apps" %}';
|
||||||
|
var options = {
|
||||||
|
ele: $('#remote_app_list_table'),
|
||||||
|
columnDefs: [
|
||||||
|
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||||
|
var name = htmlEscape(cellData);
|
||||||
|
$(td).html(name)
|
||||||
|
}},
|
||||||
|
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||||
|
var hostname = htmlEscape(cellData.hostname);
|
||||||
|
$(td).html(hostname);
|
||||||
|
}},
|
||||||
|
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||||
|
var name = htmlEscape(cellData.name);
|
||||||
|
$(td).html(name);
|
||||||
|
}},
|
||||||
|
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||||
|
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||||
|
$(td).html(conn_btn)
|
||||||
|
}}
|
||||||
|
],
|
||||||
|
ajax_url: url,
|
||||||
|
columns: [
|
||||||
|
{data: "id"},
|
||||||
|
{data: "name"},
|
||||||
|
{data: "get_type_display", orderable: false},
|
||||||
|
{data: "asset_info", orderable: false},
|
||||||
|
{data: "system_user_info", orderable: false},
|
||||||
|
{data: "comment", orderable: false},
|
||||||
|
{data: "id", orderable: false}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
remote_app_table = jumpserver.initServerSideDataTable(options);
|
||||||
|
return remote_app_table
|
||||||
|
}
|
||||||
|
$(document).ready(function(){
|
||||||
|
initTable();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,7 @@
|
||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
|
||||||
|
]
|
|
@ -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/<uuid:pk>/connection-info/',
|
||||||
|
api.RemoteAppConnectionInfoApi.as_view(),
|
||||||
|
name='remote-app-connection-info')
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns += router.urls
|
|
@ -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/<uuid:pk>/update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'),
|
||||||
|
path('remote-app/<uuid:pk>/', views.RemoteAppDetailView.as_view(), name='remote-app-detail'),
|
||||||
|
# User RemoteApp view
|
||||||
|
path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list')
|
||||||
|
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
from .remote_app import *
|
|
@ -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)
|
|
@ -20,7 +20,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
|
||||||
from common.mixins import IDInFilterMixin
|
from common.mixins import IDInCacheFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ..hands import IsOrgAdmin
|
from ..hands import IsOrgAdmin
|
||||||
from ..models import AdminUser, Asset
|
from ..models import AdminUser, Asset
|
||||||
|
@ -36,7 +36,7 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
Admin user api set, for add,delete,update,list,retrieve resource
|
Admin user api set, for add,delete,update,list,retrieve resource
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,8 +16,9 @@ from django.urls import reverse_lazy
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from common.mixins import IDInFilterMixin
|
from common.mixins import IDInCacheFilterMixin
|
||||||
from common.utils import get_logger
|
|
||||||
|
from common.utils import get_logger, get_object_or_none
|
||||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||||
from ..models import Asset, AdminUser, Node
|
from ..models import Asset, AdminUser, Node
|
||||||
|
@ -35,7 +36,7 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint that allows Asset to be viewed or edited.
|
API endpoint that allows Asset to be viewed or edited.
|
||||||
"""
|
"""
|
||||||
|
@ -47,6 +48,19 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||||
pagination_class = LimitOffsetPagination
|
pagination_class = LimitOffsetPagination
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
|
|
||||||
|
def set_assets_node(self, assets):
|
||||||
|
if not isinstance(assets, list):
|
||||||
|
assets = [assets]
|
||||||
|
node = Node.objects.get(value='Default')
|
||||||
|
node_id = self.request.query_params.get('node_id')
|
||||||
|
if node_id:
|
||||||
|
node = get_object_or_none(Node, pk=node_id)
|
||||||
|
node.assets.add(*assets)
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
assets = serializer.save()
|
||||||
|
self.set_assets_node(assets)
|
||||||
|
|
||||||
def filter_node(self, queryset):
|
def filter_node(self, queryset):
|
||||||
node_id = self.request.query_params.get("node_id")
|
node_id = self.request.query_params.get("node_id")
|
||||||
if not node_id:
|
if not node_id:
|
||||||
|
@ -89,7 +103,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
Asset bulk update api
|
Asset bulk update api
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -39,6 +39,8 @@ __all__ = [
|
||||||
|
|
||||||
|
|
||||||
class NodeViewSet(viewsets.ModelViewSet):
|
class NodeViewSet(viewsets.ModelViewSet):
|
||||||
|
filter_fields = ('value', 'key', )
|
||||||
|
search_fields = filter_fields
|
||||||
queryset = Node.objects.all()
|
queryset = Node.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.NodeSerializer
|
serializer_class = serializers.NodeSerializer
|
||||||
|
|
|
@ -21,6 +21,7 @@ from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||||
|
from common.mixins import IDInCacheFilterMixin
|
||||||
from ..models import SystemUser, Asset
|
from ..models import SystemUser, Asset
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..tasks import push_system_user_to_assets_manual, \
|
from ..tasks import push_system_user_to_assets_manual, \
|
||||||
|
@ -38,7 +39,7 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SystemUserViewSet(BulkModelViewSet):
|
class SystemUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
System user api set, for add,delete,update,list,retrieve resource
|
System user api set, for add,delete,update,list,retrieve resource
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -11,7 +11,7 @@ class AuthBookBackend(BaseBackend):
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter(cls, username=None, asset=None, latest=True):
|
def filter(cls, username=None, asset=None, latest=True):
|
||||||
queryset = AuthBook.objects.all()
|
queryset = AuthBook.objects.all()
|
||||||
if username:
|
if username is not None:
|
||||||
queryset = queryset.filter(username=username)
|
queryset = queryset.filter(username=username)
|
||||||
if asset:
|
if asset:
|
||||||
queryset = queryset.filter(asset=asset)
|
queryset = queryset.filter(asset=asset)
|
||||||
|
|
|
@ -27,7 +27,7 @@ class AdminUserBackend(BaseBackend):
|
||||||
instances = []
|
instances = []
|
||||||
assets = cls._get_assets(asset)
|
assets = cls._get_assets(asset)
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
if username and asset.admin_user.username != username:
|
if username is not None and asset.admin_user.username != username:
|
||||||
continue
|
continue
|
||||||
instance = construct_authbook_object(asset.admin_user, asset)
|
instance = construct_authbook_object(asset.admin_user, asset)
|
||||||
instances.append(instance)
|
instances.append(instance)
|
||||||
|
|
|
@ -30,7 +30,7 @@ class SystemUserBackend(BaseBackend):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _filter_system_users_by_username(cls, system_users, username):
|
def _filter_system_users_by_username(cls, system_users, username):
|
||||||
_system_users = cls._distinct_system_users_by_username(system_users)
|
_system_users = cls._distinct_system_users_by_username(system_users)
|
||||||
if username:
|
if username is not None:
|
||||||
_system_users = [su for su in _system_users if username == su.username]
|
_system_users = [su for su in _system_users if username == su.username]
|
||||||
return _system_users
|
return _system_users
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
UPDATE_ASSETS_HARDWARE_TASKS = [
|
UPDATE_ASSETS_HARDWARE_TASKS = [
|
||||||
{
|
{
|
||||||
'name': "setup",
|
'name': "setup",
|
||||||
|
@ -51,3 +54,4 @@ TASK_OPTIONS = {
|
||||||
|
|
||||||
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
|
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-05-21 09:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0026_auto_20190325_2035'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='ip',
|
||||||
|
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,3 +8,4 @@ from .asset import *
|
||||||
from .cmd_filter import *
|
from .cmd_filter import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from .authbook import *
|
from .authbook import *
|
||||||
|
from applications.models.remote_app import *
|
||||||
|
|
|
@ -71,7 +71,7 @@ class Asset(OrgModelMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||||
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
|
|
|
@ -78,7 +78,7 @@ class AuthBook(AssetUser):
|
||||||
if host == self.asset.hostname:
|
if host == self.asset.hostname:
|
||||||
_connectivity = self.UNREACHABLE
|
_connectivity = self.UNREACHABLE
|
||||||
|
|
||||||
for host in value.get('contacted', {}).keys():
|
for host in value.get('contacted', []):
|
||||||
if host == self.asset.hostname:
|
if host == self.asset.hostname:
|
||||||
_connectivity = self.REACHABLE
|
_connectivity = self.REACHABLE
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
|
@ -15,14 +16,29 @@ class AdminUserSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
管理用户
|
管理用户
|
||||||
"""
|
"""
|
||||||
assets_amount = serializers.SerializerMethodField()
|
password = serializers.CharField(
|
||||||
unreachable_amount = serializers.SerializerMethodField()
|
required=False, write_only=True, label=_('Password')
|
||||||
reachable_amount = serializers.SerializerMethodField()
|
)
|
||||||
|
unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable'))
|
||||||
|
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
|
||||||
|
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
list_serializer_class = AdaptedBulkListSerializer
|
||||||
model = AdminUser
|
model = AdminUser
|
||||||
fields = '__all__'
|
fields = [
|
||||||
|
'id', 'org_id', 'name', 'username', 'assets_amount',
|
||||||
|
'reachable_amount', 'unreachable_amount', 'password', 'comment',
|
||||||
|
'date_created', 'date_updated', 'become', 'become_method',
|
||||||
|
'become_user', 'created_by',
|
||||||
|
]
|
||||||
|
|
||||||
|
extra_kwargs = {
|
||||||
|
'date_created': {'label': _('Date created')},
|
||||||
|
'date_updated': {'label': _('Date updated')},
|
||||||
|
'become': {'read_only': True}, 'become_method': {'read_only': True},
|
||||||
|
'become_user': {'read_only': True}, 'created_by': {'read_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
def get_field_names(self, declared_fields, info):
|
def get_field_names(self, declared_fields, info):
|
||||||
fields = super().get_field_names(declared_fields, info)
|
fields = super().get_field_names(declared_fields, info)
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
#
|
#
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orgs.mixins import OrgResourceSerializerMixin
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from ..models import Asset
|
from ..models import Asset
|
||||||
|
@ -13,15 +16,35 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin):
|
||||||
"""
|
"""
|
||||||
资产的数据结构
|
资产的数据结构
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
list_serializer_class = AdaptedBulkListSerializer
|
||||||
fields = '__all__'
|
# validators = [] # 解决批量导入时unique_together字段校验失败
|
||||||
validators = []
|
fields = [
|
||||||
|
'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port',
|
||||||
|
'platform', 'is_active', 'public_ip', 'domain', 'admin_user',
|
||||||
|
'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
|
||||||
|
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
|
||||||
|
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
|
||||||
|
'hostname_raw', 'comment', 'created_by', 'date_created',
|
||||||
|
'hardware_info', 'connectivity'
|
||||||
|
]
|
||||||
|
read_only_fields = (
|
||||||
|
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||||
|
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||||
|
'os', 'os_version', 'os_arch', 'hostname_raw',
|
||||||
|
'created_by', 'date_created',
|
||||||
|
)
|
||||||
|
extra_kwargs = {
|
||||||
|
'hardware_info': {'label': _('Hardware info')},
|
||||||
|
'connectivity': {'label': _('Connectivity')},
|
||||||
|
'org_name': {'label': _('Org name')}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_eager_loading(cls, queryset):
|
def setup_eager_loading(cls, queryset):
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
|
|
||||||
from ..models import SystemUser, Asset
|
from ..models import SystemUser, Asset
|
||||||
|
@ -10,16 +12,36 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
系统用户
|
系统用户
|
||||||
"""
|
"""
|
||||||
unreachable_amount = serializers.SerializerMethodField()
|
password = serializers.CharField(
|
||||||
reachable_amount = serializers.SerializerMethodField()
|
required=False, write_only=True, label=_('Password')
|
||||||
unreachable_assets = serializers.SerializerMethodField()
|
)
|
||||||
reachable_assets = serializers.SerializerMethodField()
|
unreachable_amount = serializers.SerializerMethodField(
|
||||||
assets_amount = serializers.SerializerMethodField()
|
label=_('Unreachable')
|
||||||
|
)
|
||||||
|
unreachable_assets = serializers.SerializerMethodField(
|
||||||
|
label=_('Unreachable assets')
|
||||||
|
)
|
||||||
|
reachable_assets = serializers.SerializerMethodField(
|
||||||
|
label=_('Reachable assets')
|
||||||
|
)
|
||||||
|
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
|
||||||
|
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
exclude = ('_password', '_private_key', '_public_key')
|
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
list_serializer_class = AdaptedBulkListSerializer
|
||||||
|
fields = [
|
||||||
|
'id', 'org_id', 'name', 'username', 'login_mode',
|
||||||
|
'login_mode_display', 'priority', 'protocol', 'auto_push',
|
||||||
|
'password', 'assets_amount', 'reachable_amount', 'reachable_assets',
|
||||||
|
'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo',
|
||||||
|
'shell', 'comment', 'nodes', 'assets'
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'login_mode_display': {'label': _('Login mode display')},
|
||||||
|
'created_by': {'read_only': True}, 'nodes': {'read_only': True},
|
||||||
|
'assets': {'read_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
def get_field_names(self, declared_fields, info):
|
def get_field_names(self, declared_fields, info):
|
||||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends '_import_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_title%}{% trans "Import admin user" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block import_modal_download_template_url %}{% url "api-assets:admin-user-list" %}{% endblock %}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends '_update_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_title%}{% trans "Update admin user" %}{% endblock %}
|
|
@ -1,29 +1,6 @@
|
||||||
{% extends '_modal.html' %}
|
{% extends '_import_modal.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block modal_id %}asset_import_modal{% endblock %}
|
|
||||||
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
|
{% block modal_title%}{% trans "Import assets" %}{% endblock %}
|
||||||
{% block modal_body %}
|
|
||||||
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
|
{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %}
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
|
|
||||||
<a href="{% url 'assets:asset-export' %}" style="display: block">{% trans 'Download' %}</a>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="id_users">{% trans "Asset csv file" %}</label>
|
|
||||||
<input id="id_assets" type="file" name="file" />
|
|
||||||
<span class="help-block red-fonts">
|
|
||||||
{% trans 'If set id, will use this id update asset existed' %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<p>
|
|
||||||
<p class="text-success" id="id_created"></p>
|
|
||||||
<p id="id_created_detail"></p>
|
|
||||||
<p class="text-warning" id="id_updated"></p>
|
|
||||||
<p id="id_updated_detail"></p>
|
|
||||||
<p class="text-danger" id="id_failed"></p>
|
|
||||||
<p id="id_failed_detail"></p>
|
|
||||||
</p>
|
|
||||||
{% endblock %}
|
|
||||||
{% block modal_confirm_id %}btn_asset_import{% endblock %}
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends '_update_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_title%}{% trans "Update assets" %}{% endblock %}
|
|
@ -0,0 +1,140 @@
|
||||||
|
{% extends '_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% block modal_id %}asset_user_auth_view{% endblock %}
|
||||||
|
{% block modal_title%}{% trans "Asset user auth" %}{% endblock %}
|
||||||
|
{% block modal_body %}
|
||||||
|
<style>
|
||||||
|
.inmodal .modal-body {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<form class="form-horizontal" action="" style="padding-top: 20px">
|
||||||
|
<div class="form-group mfa-field">
|
||||||
|
<label for="mfa" class="col-sm-2 control-label">{% trans 'MFA' %}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" id="mfa" class="form-control input-sm" name="mfa">
|
||||||
|
<span id="mfa_error" class="help-block">{% trans "Need otp auth for view auth" %}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<a class="btn btn-primary btn-sm btn-mfa">{% trans "Confirm" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div hidden class="auth-field">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<p class="form-control-static" id="id_hostname_view"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="" class="col-sm-2 control-label">{% trans 'Username' %}</label>
|
||||||
|
<div class="col-sm-8" >
|
||||||
|
<p class="form-control-static" id="id_username_view"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="" class="col-sm-2 control-label">{% trans 'Password' %}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input id="id_password_view" type="password" class="form-control" value="" readonly style="border: none;padding-left: 0;background-color: #fff;width: 100%">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2" style="padding-left: 2px">
|
||||||
|
<a class="btn btn-white btn-sm btn-show-password"><i class="fa fa-eye"></i></a>
|
||||||
|
<a class="btn btn-white btn-sm btn-copy-password"><i class="fa fa-copy"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script>
|
||||||
|
<script>
|
||||||
|
var showPassword = false;
|
||||||
|
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}";
|
||||||
|
var asset_id = "";
|
||||||
|
var host = "";
|
||||||
|
var username = "";
|
||||||
|
|
||||||
|
function initClipboard() {
|
||||||
|
var clipboard = new Clipboard('.btn-copy-password', {
|
||||||
|
text: function (trigger) {
|
||||||
|
return $("#id_password_view").val()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
clipboard.on("success", function (e) {
|
||||||
|
toastr.success("{% trans "Copy success" %}")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAuth() {
|
||||||
|
$(".mfa-field").hide();
|
||||||
|
$(".auth-field").show();
|
||||||
|
|
||||||
|
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + asset_id + "&username=" + username;
|
||||||
|
$("#id_username_view").html(username);
|
||||||
|
$("#id_hostname_view").html(host);
|
||||||
|
var success = function (data) {
|
||||||
|
var password = data.password;
|
||||||
|
$("#id_password_view").val(password);
|
||||||
|
};
|
||||||
|
var error = function() {
|
||||||
|
var msg = "{% trans 'Get auth info error' %}";
|
||||||
|
toastr.error(msg)
|
||||||
|
};
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
|
success: success,
|
||||||
|
flash_message: false,
|
||||||
|
error: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMFA() {
|
||||||
|
$(".mfa-field").show();
|
||||||
|
$(".auth-field").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
initClipboard();
|
||||||
|
}).on("click", ".btn-show-password", function () {
|
||||||
|
showPassword = !showPassword;
|
||||||
|
if (showPassword) {
|
||||||
|
$("#id_password_view").attr("type", "text")
|
||||||
|
} else {
|
||||||
|
$("#id_password_view").attr("type", "password")
|
||||||
|
}
|
||||||
|
}).on("show.bs.modal", "#asset_user_auth_view", function () {
|
||||||
|
var now = new Date();
|
||||||
|
if (lastMFATime === "") {
|
||||||
|
lastMFATime = 0
|
||||||
|
}
|
||||||
|
var nowTime = now.getTime() / 1000;
|
||||||
|
if (nowTime - lastMFATime < 60*10 ) {
|
||||||
|
showAuth();
|
||||||
|
}
|
||||||
|
}).on("click", ".btn-mfa", function () {
|
||||||
|
var url = "{% url 'api-auth:user-otp-verify' %}";
|
||||||
|
var data = {
|
||||||
|
code: $("#mfa").val()
|
||||||
|
};
|
||||||
|
var success = function () {
|
||||||
|
var now = new Date();
|
||||||
|
lastMFATime = now.getTime() / 1000;
|
||||||
|
showAuth()
|
||||||
|
};
|
||||||
|
var error = function () {
|
||||||
|
$("#mfa_error").addClass("text-danger").html("Code error");
|
||||||
|
};
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: url,
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success: success,
|
||||||
|
flash_message: false,
|
||||||
|
error: error
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
{% block modal_button %}
|
||||||
|
<button data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends '_import_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_title%}{% trans "Import system user" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block import_modal_download_template_url %}{% url "api-assets:system-user-list" %}{% endblock %}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends '_update_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_title%}{% trans "Update system user" %}{% endblock %}
|
|
@ -85,6 +85,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||||
|
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
|
@ -112,9 +113,12 @@ function initTable() {
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||||
|
var btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||||
|
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "View auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||||
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
btn += view_btn;
|
||||||
$(td).html(test_btn + update_auth_btn);
|
btn += test_btn;
|
||||||
|
$(td).html(btn);
|
||||||
}}
|
}}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -201,5 +205,11 @@ $(document).ready(function () {
|
||||||
$('#id_password').parent().addClass('has-error');
|
$('#id_password').parent().addClass('has-error');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.on("click", ".btn-view-auth", function (evt) {
|
||||||
|
asset_id = $(this).data("aid") ;
|
||||||
|
host = $(this).data("hostname");
|
||||||
|
username = "{{ admin_user.username }}";
|
||||||
|
$("#asset_user_auth_view").modal();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
{% extends '_base_list.html' %}
|
{% extends '_base_list.html' %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block table_search %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block help_message %}
|
{% block help_message %}
|
||||||
<div class="alert alert-info help-message">
|
<div class="alert alert-info help-message">
|
||||||
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
|
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
|
||||||
|
@ -12,6 +9,30 @@
|
||||||
{% trans 'You can set any one for Windows or other hardware.' %}
|
{% trans 'You can set any one for Windows or other hardware.' %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block table_search %}
|
||||||
|
<div class="" style="float: right">
|
||||||
|
<div class=" btn-group">
|
||||||
|
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a class=" btn_export" tabindex="0">
|
||||||
|
<span>{% trans "Export" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||||
|
<span>{% trans "Import" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||||
|
<span>{% trans "Update" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<div class="uc pull-left m-r-5">
|
<div class="uc pull-left m-r-5">
|
||||||
|
@ -36,11 +57,14 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% include 'assets/_admin_user_import_modal.html' %}
|
||||||
|
{% include 'assets/_admin_user_update_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_bottom_left %}{% endblock %}
|
{% block content_bottom_left %}{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function(){
|
var admin_user_table = 0;
|
||||||
|
function initTable() {
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#admin_user_list_table'),
|
ele: $('#admin_user_list_table'),
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
|
@ -93,7 +117,12 @@ $(document).ready(function(){
|
||||||
columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
|
columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
|
||||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}]
|
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}]
|
||||||
};
|
};
|
||||||
jumpserver.initServerSideDataTable(options)
|
admin_user_table = jumpserver.initServerSideDataTable(options);
|
||||||
|
return admin_user_table
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
initTable()
|
||||||
})
|
})
|
||||||
|
|
||||||
.on('click', '.btn_admin_user_delete', function () {
|
.on('click', '.btn_admin_user_delete', function () {
|
||||||
|
@ -107,6 +136,70 @@ $(document).ready(function(){
|
||||||
$data_table.ajax.reload();
|
$data_table.ajax.reload();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
});
|
})
|
||||||
|
.on('click', '.btn_export', function(){
|
||||||
|
var admin_users = admin_user_table.selected;
|
||||||
|
var data = {
|
||||||
|
'resources': admin_users
|
||||||
|
};
|
||||||
|
var search = $("input[type='search']").val();
|
||||||
|
var props = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success_url: "{% url 'api-assets:admin-user-list' %}",
|
||||||
|
format: "csv",
|
||||||
|
params: {
|
||||||
|
search: search
|
||||||
|
}
|
||||||
|
};
|
||||||
|
APIExportData(props);
|
||||||
|
}).on('click', '#btn_import_confirm',function () {
|
||||||
|
var url = "{% url 'api-assets:admin-user-list' %}";
|
||||||
|
var file = document.getElementById('id_file').files[0];
|
||||||
|
if(!file){
|
||||||
|
toastr.error("{% trans "Please select file" %}");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var data_table = $('#admin_user_list_table').DataTable();
|
||||||
|
APIImportData({
|
||||||
|
url: url,
|
||||||
|
method: "POST",
|
||||||
|
body: file,
|
||||||
|
data_table: data_table
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on('click', '#download_update_template', function () {
|
||||||
|
var admin_users = admin_user_table.selected;
|
||||||
|
var data = {
|
||||||
|
'resources': admin_users
|
||||||
|
};
|
||||||
|
var search = $("input[type='search']").val();
|
||||||
|
var props = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success_url: "{% url 'api-assets:admin-user-list' %}?format=csv&template=update",
|
||||||
|
format: 'csv',
|
||||||
|
params: {
|
||||||
|
search: search
|
||||||
|
}
|
||||||
|
};
|
||||||
|
APIExportData(props);
|
||||||
|
})
|
||||||
|
.on('click', '#btn_update_confirm', function () {
|
||||||
|
var file = document.getElementById('update_file').files[0];
|
||||||
|
if(!file){
|
||||||
|
toastr.error("{% trans "Please select file" %}");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var url = "{% url 'api-assets:admin-user-list' %}";
|
||||||
|
var data_table = $('#admin_user_list_table').DataTable();
|
||||||
|
|
||||||
|
APIImportData({
|
||||||
|
url: url,
|
||||||
|
method: "PUT",
|
||||||
|
body: file,
|
||||||
|
data_table: data_table
|
||||||
|
});
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
{% load common_tags %}
|
{% load common_tags %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block custom_head_css_js %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="wrapper wrapper-content animated fadeInRight">
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -87,6 +83,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||||
|
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
|
@ -117,14 +114,14 @@ function initAssetUserTable() {
|
||||||
$(td).html(cellData.slice(0, -6));
|
$(td).html(cellData.slice(0, -6));
|
||||||
}},
|
}},
|
||||||
{targets: 5, createdCell: function (td, cellData) {
|
{targets: 5, createdCell: function (td, cellData) {
|
||||||
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Update auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
var btn = '<a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Update auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||||
{% if asset.protocol == 'ssh' %}
|
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-username="DEFAULT_USERNAME">{% trans "View auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||||
$(td).html(test_btn + update_auth_btn);
|
btn += view_btn;
|
||||||
{% else %}
|
{% if asset.protocol == 'ssh' %}
|
||||||
$(td).html(update_auth_btn);
|
btn += test_btn;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{#var check_btn = ' <a class="btn btn-xs btn-info btn-check-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Check auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);#}
|
$(td).html(btn);
|
||||||
|
|
||||||
}}
|
}}
|
||||||
],
|
],
|
||||||
|
@ -142,17 +139,6 @@ var username;
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
initAssetUserTable();
|
initAssetUserTable();
|
||||||
})
|
})
|
||||||
{#.on('click', '.btn-check-asset-user-auth', function(){#}
|
|
||||||
{# var username = $(this).data('username');#}
|
|
||||||
{# var the_url = "{% url 'api-assets:asset-user-auth-info' %}" + '?asset_id={{ asset.id }}' + '&username=' + username;#}
|
|
||||||
{# $.ajax({#}
|
|
||||||
{# url: the_url,#}
|
|
||||||
{# method: 'GET',#}
|
|
||||||
{# success: function (data) {#}
|
|
||||||
{# alert("Password: " + data.password);#}
|
|
||||||
{# }#}
|
|
||||||
{# });#}
|
|
||||||
{# })#}
|
|
||||||
.on('click', '.btn-update-asset-user-auth', function() {
|
.on('click', '.btn-update-asset-user-auth', function() {
|
||||||
username = $(this).data('username');
|
username = $(this).data('username');
|
||||||
var hostname = "{{ asset.hostname }}";
|
var hostname = "{{ asset.hostname }}";
|
||||||
|
@ -214,5 +200,11 @@ $(document).ready(function () {
|
||||||
flash_message: false
|
flash_message: false
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
.on("click", ".btn-view-auth", function (evt) {
|
||||||
|
asset_id = "{{ asset.id }}" ;
|
||||||
|
host = "{{ asset.hostname }}";
|
||||||
|
username = $(this).data("username");
|
||||||
|
$("#asset_user_auth_view").modal();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -67,14 +67,26 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mail-box-header">
|
<div class="mail-box-header">
|
||||||
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||||
<div class="html5buttons">
|
<div class="" style="float: right">
|
||||||
<div class="dt-buttons btn-group">
|
<div class=" btn-group">
|
||||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||||
<span>{% trans "Import" %}</span>
|
<ul class="dropdown-menu">
|
||||||
</a>
|
<li>
|
||||||
<a class="btn btn-default btn_export" tabindex="0">
|
<a class=" btn_export" tabindex="0">
|
||||||
<span>{% trans "Export" %}</span>
|
<span>{% trans "Export" %}</span>
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||||
|
<span>{% trans "Import" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||||
|
<span>{% trans "Update" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group" style="float: right">
|
<div class="btn-group" style="float: right">
|
||||||
|
@ -140,7 +152,7 @@
|
||||||
{# <li id="fresh_tree" class="btn-refresh-tree" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh' %}</a></li>#}
|
{# <li id="fresh_tree" class="btn-refresh-tree" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh' %}</a></li>#}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'assets/_asset_update_modal.html' %}
|
||||||
{% include 'assets/_asset_import_modal.html' %}
|
{% include 'assets/_asset_import_modal.html' %}
|
||||||
{% include 'assets/_asset_list_modal.html' %}
|
{% include 'assets/_asset_list_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -457,49 +469,78 @@ $(document).ready(function(){
|
||||||
asset_table.search(val).draw();
|
asset_table.search(val).draw();
|
||||||
})
|
})
|
||||||
.on('click', '.btn_export', function () {
|
.on('click', '.btn_export', function () {
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
var assets = asset_table.selected;
|
||||||
var rows = $data_table.rows('.selected').data();
|
var data = {
|
||||||
|
'resources': assets
|
||||||
var assets = [];
|
};
|
||||||
$.each(rows, function (index, obj) {
|
var search = $("input[type='search']").val();
|
||||||
assets.push(obj.id)
|
var props = {
|
||||||
});
|
method: "POST",
|
||||||
$.ajax({
|
body: JSON.stringify(data),
|
||||||
url: "{% url "assets:asset-export" %}",
|
success_url: "{% url 'api-assets:asset-list' %}",
|
||||||
method: 'POST',
|
format: 'csv',
|
||||||
data: JSON.stringify({assets_id: assets, node_id: current_node_id}),
|
params: {
|
||||||
dataType: "json",
|
search: search,
|
||||||
success: function (data, textStatus) {
|
node_id: current_node_id || ''
|
||||||
window.open(data.redirect)
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
toastr.error('Export failed');
|
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
|
APIExportData(props);
|
||||||
})
|
})
|
||||||
.on('click', '#btn_asset_import', function () {
|
.on('click', '#btn_import_confirm', function () {
|
||||||
var $form = $('#fm_asset_import');
|
var file = document.getElementById('id_file').files[0];
|
||||||
var action = $form.attr("action");
|
if(!file){
|
||||||
|
toastr.error("{% trans "Please select file" %}");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var url = "{% url 'api-assets:asset-list' %}";
|
||||||
if (current_node_id){
|
if (current_node_id){
|
||||||
action = setUrlParam(action, 'node_id', current_node_id);
|
url = setUrlParam(url, 'node_id', current_node_id);
|
||||||
$form.attr("action", action)
|
|
||||||
}
|
}
|
||||||
$form.find('.help-block').remove();
|
var data_table = $('#asset_list_table').DataTable();
|
||||||
function success (data) {
|
|
||||||
if (data.valid === false) {
|
APIImportData({
|
||||||
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
|
url: url,
|
||||||
} else {
|
method: "POST",
|
||||||
$('#id_created').html(data.created_info);
|
body: file,
|
||||||
$('#id_created_detail').html(data.created.join(', '));
|
data_table: data_table
|
||||||
$('#id_updated').html(data.updated_info);
|
});
|
||||||
$('#id_updated_detail').html(data.updated.join(', '));
|
})
|
||||||
$('#id_failed').html(data.failed_info);
|
.on('click', '#download_update_template', function () {
|
||||||
$('#id_failed_detail').html(data.failed.join(', '));
|
var assets = asset_table.selected;
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
var data = {
|
||||||
$data_table.ajax.reload();
|
'resources': assets
|
||||||
|
};
|
||||||
|
var search = $("input[type='search']").val();
|
||||||
|
var props = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success_url: "{% url 'api-assets:asset-list' %}?format=csv&template=update",
|
||||||
|
format: 'csv',
|
||||||
|
params: {
|
||||||
|
search: search,
|
||||||
|
node_id: current_node_id || ''
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
APIExportData(props);
|
||||||
|
})
|
||||||
|
.on('click', '#btn_update_confirm', function () {
|
||||||
|
var file = document.getElementById('update_file').files[0];
|
||||||
|
if(!file){
|
||||||
|
toastr.error("{% trans "Please select file" %}");
|
||||||
|
return
|
||||||
}
|
}
|
||||||
$form.ajaxSubmit({success: success});
|
var url = "{% url 'api-assets:asset-list' %}";
|
||||||
|
if (current_node_id){
|
||||||
|
url = setUrlParam(url, 'node_id', current_node_id);
|
||||||
|
}
|
||||||
|
var data_table = $('#asset_list_table').DataTable();
|
||||||
|
|
||||||
|
APIImportData({
|
||||||
|
url: url,
|
||||||
|
method: "PUT",
|
||||||
|
body: file,
|
||||||
|
data_table: data_table
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.on('click', '.btn-create-asset', function () {
|
.on('click', '.btn-create-asset', function () {
|
||||||
var url = "{% url 'assets:asset-create' %}";
|
var url = "{% url 'assets:asset-create' %}";
|
||||||
|
@ -584,15 +625,17 @@ $(document).ready(function(){
|
||||||
})
|
})
|
||||||
.on('click', '#btn_bulk_update', function () {
|
.on('click', '#btn_bulk_update', function () {
|
||||||
var action = $('#slct_bulk_update').val();
|
var action = $('#slct_bulk_update').val();
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
var id_list = asset_table.selected;
|
||||||
var id_list = [];
|
|
||||||
$data_table.rows({selected: true}).every(function(){
|
|
||||||
id_list.push(this.data().id);
|
|
||||||
});
|
|
||||||
if (id_list.length === 0) {
|
if (id_list.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
var the_url = "{% url 'api-assets:asset-list' %}";
|
||||||
|
var data = {
|
||||||
|
'resources': id_list
|
||||||
|
};
|
||||||
|
function refreshTag() {
|
||||||
|
$('#asset_list_table').DataTable().ajax.reload();
|
||||||
|
}
|
||||||
|
|
||||||
function doDeactive() {
|
function doDeactive() {
|
||||||
var data = [];
|
var data = [];
|
||||||
|
@ -601,7 +644,8 @@ $(document).ready(function(){
|
||||||
data.push(obj);
|
data.push(obj);
|
||||||
});
|
});
|
||||||
function success() {
|
function success() {
|
||||||
asset_table.ajax.reload()
|
setTimeout( function () {
|
||||||
|
window.location.reload();}, 500);
|
||||||
}
|
}
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -617,7 +661,8 @@ $(document).ready(function(){
|
||||||
data.push(obj);
|
data.push(obj);
|
||||||
});
|
});
|
||||||
function success() {
|
function success() {
|
||||||
asset_table.ajax.reload()
|
setTimeout( function () {
|
||||||
|
window.location.reload();}, 300);
|
||||||
}
|
}
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -636,44 +681,47 @@ $(document).ready(function(){
|
||||||
confirmButtonColor: "#DD6B55",
|
confirmButtonColor: "#DD6B55",
|
||||||
confirmButtonText: "{% trans 'Confirm' %}",
|
confirmButtonText: "{% trans 'Confirm' %}",
|
||||||
closeOnConfirm: false
|
closeOnConfirm: false
|
||||||
}, function() {
|
},function () {
|
||||||
var success = function() {
|
function success(data) {
|
||||||
|
url = setUrlParam(the_url, 'spm', data.spm);
|
||||||
|
APIUpdateAttr({
|
||||||
|
url:url,
|
||||||
|
method:'DELETE',
|
||||||
|
success:refreshTag,
|
||||||
|
flash_message:false,
|
||||||
|
});
|
||||||
var msg = "{% trans 'Asset Deleted.' %}";
|
var msg = "{% trans 'Asset Deleted.' %}";
|
||||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||||
$('#asset_list_table').DataTable().ajax.reload();
|
}
|
||||||
};
|
function fail() {
|
||||||
var fail = function() {
|
|
||||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||||
swal("{% trans 'Asset Delete' %}", msg, "error");
|
swal("{% trans 'Asset Delete' %}", msg, "error");
|
||||||
};
|
|
||||||
var url_delete = the_url + '?id__in=' + JSON.stringify(id_list);
|
|
||||||
APIUpdateAttr({
|
|
||||||
url: url_delete,
|
|
||||||
method: 'DELETE',
|
|
||||||
success: success,
|
|
||||||
error: fail
|
|
||||||
});
|
|
||||||
$data_table.ajax.reload();
|
|
||||||
jumpserver.checked = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: "{% url 'api-common:resources-cache' %}",
|
||||||
|
method:'POST',
|
||||||
|
body:JSON.stringify(data),
|
||||||
|
success:success,
|
||||||
|
error:fail
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function doUpdate() {
|
function doUpdate() {
|
||||||
var data = {
|
function fail(data) {
|
||||||
'assets_id':id_list
|
toastr.error(JSON.parse(data))
|
||||||
};
|
|
||||||
function error(data) {
|
|
||||||
toastr.error(JSON.parse(data).error)
|
|
||||||
}
|
}
|
||||||
function success(data) {
|
function success(data) {
|
||||||
location.href = data.url;
|
var url = "{% url 'assets:asset-bulk-update' %}";
|
||||||
|
location.href= setUrlParam(url, 'spm', data.spm);
|
||||||
}
|
}
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
'url': "{% url 'api-assets:asset-bulk-update-select' %}",
|
url: "{% url 'api-common:resources-cache' %}",
|
||||||
'method': 'POST',
|
method:'POST',
|
||||||
'body': JSON.stringify(data),
|
body:JSON.stringify(data),
|
||||||
'flash_message': false,
|
flash_message:false,
|
||||||
'success': success,
|
success:success,
|
||||||
'error': error,
|
error:fail
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -698,6 +746,7 @@ $(document).ready(function(){
|
||||||
'success': success
|
'success': success
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case 'deactive':
|
case 'deactive':
|
||||||
doDeactive();
|
doDeactive();
|
||||||
|
|
|
@ -133,6 +133,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||||
|
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
|
@ -160,12 +161,13 @@ function initAssetsTable() {
|
||||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||||
var push_btn = '';
|
var push_btn = '';
|
||||||
{% if system_user.auto_push %}
|
{% if system_user.auto_push %}
|
||||||
push_btn = '<a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
push_btn = ' <a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||||
{% endif %}
|
{% endif %}
|
||||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||||
|
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "View auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||||
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
|
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
|
||||||
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||||
$(td).html(push_btn + test_btn + update_auth_btn);
|
$(td).html(update_auth_btn + view_btn + push_btn + test_btn);
|
||||||
}}
|
}}
|
||||||
],
|
],
|
||||||
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
|
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
|
||||||
|
@ -360,5 +362,11 @@ $(document).ready(function () {
|
||||||
$('#id_password').parent().addClass('has-error');
|
$('#id_password').parent().addClass('has-error');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.on("click", ".btn-view-auth", function (evt) {
|
||||||
|
asset_id = $(this).data("aid") ;
|
||||||
|
host = $(this).data("hostname");
|
||||||
|
username = "{{ system_user.username }}";
|
||||||
|
$("#asset_user_auth_view").modal();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -14,6 +14,28 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
|
<div class="" style="float: right">
|
||||||
|
<div class=" btn-group">
|
||||||
|
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a class=" btn_export" tabindex="0">
|
||||||
|
<span>{% trans "Export" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||||
|
<span>{% trans "Import" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||||
|
<span>{% trans "Update" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
|
@ -41,9 +63,12 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% include 'assets/_system_user_import_modal.html' %}
|
||||||
|
{% include 'assets/_system_user_update_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
|
var system_user_table = 0;
|
||||||
function initTable() {
|
function initTable() {
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#system_user_list_table'),
|
ele: $('#system_user_list_table'),
|
||||||
|
@ -101,7 +126,8 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initServerSideDataTable(options);
|
system_user_table = jumpserver.initServerSideDataTable(options);
|
||||||
|
return system_user_table
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
|
@ -173,6 +199,71 @@ $(document).ready(function(){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.on('click', '.btn_export', function () {
|
||||||
|
var system_users = system_user_table.selected;
|
||||||
|
var data = {
|
||||||
|
'resources': system_users
|
||||||
|
};
|
||||||
|
var search = $("input[type='search']").val();
|
||||||
|
var props = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success_url: "{% url 'api-assets:system-user-list' %}",
|
||||||
|
format: "csv",
|
||||||
|
params:{
|
||||||
|
search:search
|
||||||
|
}
|
||||||
|
};
|
||||||
|
APIExportData(props);
|
||||||
|
})
|
||||||
|
.on('click', '#btn_import_confirm', function () {
|
||||||
|
var url = "{% url 'api-assets:system-user-list' %}";
|
||||||
|
var file = document.getElementById('id_file').files[0];
|
||||||
|
if(!file){
|
||||||
|
toastr.error("{% trans 'Please select file' %}");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var data_table = $('#system_user_list_table').DataTable();
|
||||||
|
APIImportData({
|
||||||
|
url: url,
|
||||||
|
method: "POST",
|
||||||
|
body: file,
|
||||||
|
data_table: data_table
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on('click', '#download_update_template', function () {
|
||||||
|
var system_users = system_user_table.selected;
|
||||||
|
var data = {
|
||||||
|
'resources': system_users
|
||||||
|
};
|
||||||
|
var search = $("input[type='search']").val();
|
||||||
|
var props = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success_url: "{% url 'api-assets:system-user-list' %}?format=csv&template=update",
|
||||||
|
format: "csv",
|
||||||
|
params:{
|
||||||
|
search:search
|
||||||
|
}
|
||||||
|
};
|
||||||
|
APIExportData(props);
|
||||||
|
})
|
||||||
|
.on('click', '#btn_update_confirm', function () {
|
||||||
|
var file = document.getElementById('update_file').files[0];
|
||||||
|
if(!file){
|
||||||
|
toastr.error("{% trans "Please select file" %}");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var url = "{% url 'api-assets:system-user-list' %}";
|
||||||
|
var data_table = $('#system_user_list_table').DataTable();
|
||||||
|
|
||||||
|
APIImportData({
|
||||||
|
url: url,
|
||||||
|
method: "PUT",
|
||||||
|
body: file,
|
||||||
|
data_table: data_table
|
||||||
|
});
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,9 @@ from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from common.mixins import JSONResponseMixin
|
from common.mixins import JSONResponseMixin
|
||||||
from common.utils import get_object_or_none, get_logger
|
from common.utils import get_object_or_none, get_logger
|
||||||
from common.permissions import AdminUserRequiredMixin
|
from common.permissions import AdminUserRequiredMixin
|
||||||
from common.const import create_success_msg, update_success_msg
|
from common.const import (
|
||||||
|
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
|
||||||
|
)
|
||||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from .. import forms
|
from .. import forms
|
||||||
|
@ -122,7 +124,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
spm = request.GET.get('spm', '')
|
spm = request.GET.get('spm', '')
|
||||||
assets_id = cache.get(CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm))
|
assets_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm))
|
||||||
if kwargs.get('form'):
|
if kwargs.get('form'):
|
||||||
self.form = kwargs['form']
|
self.form = kwargs['form']
|
||||||
elif assets_id:
|
elif assets_id:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
import time
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -10,10 +11,11 @@ from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.generics import CreateAPIView
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from common.utils import get_logger, get_request_ip
|
from common.utils import get_logger, get_request_ip
|
||||||
from common.permissions import IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
|
||||||
from orgs.mixins import RootOrgViewMixin
|
from orgs.mixins import RootOrgViewMixin
|
||||||
from users.serializers import UserSerializer
|
from users.serializers import UserSerializer
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
@ -23,12 +25,13 @@ from users.utils import (
|
||||||
check_user_valid, check_otp_code, increase_login_failed_count,
|
check_user_valid, check_otp_code, increase_login_failed_count,
|
||||||
is_block_login, clean_failed_count
|
is_block_login, clean_failed_count
|
||||||
)
|
)
|
||||||
|
from ..serializers import OtpVerifySerializer
|
||||||
from ..signals import post_auth_success, post_auth_failed
|
from ..signals import post_auth_success, post_auth_failed
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi',
|
'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi',
|
||||||
|
'UserOtpVerifyApi',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -179,3 +182,20 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView):
|
||||||
sender=self.__class__, username=username,
|
sender=self.__class__, username=username,
|
||||||
request=self.request, reason=reason
|
request=self.request, reason=reason
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpVerifyApi(CreateAPIView):
|
||||||
|
permission_classes = (IsValidUser,)
|
||||||
|
serializer_class = OtpVerifySerializer
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
code = serializer.validated_data["code"]
|
||||||
|
|
||||||
|
if request.user.check_otp(code):
|
||||||
|
request.session["OTP_LAST_VERIFY_TIME"] = int(time.time())
|
||||||
|
return Response({"ok": "1"})
|
||||||
|
else:
|
||||||
|
return Response({"error": "Code not valid"}, status=400)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||||
from django_auth_ldap.backend import _LDAPUser, LDAPBackend
|
from django_auth_ldap.backend import _LDAPUser, LDAPBackend
|
||||||
from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion
|
from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion
|
||||||
|
|
||||||
|
from users.utils import construct_user_email
|
||||||
|
|
||||||
logger = _LDAPConfig.get_logger()
|
logger = _LDAPConfig.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,13 +88,18 @@ class LDAPUser(_LDAPUser):
|
||||||
return user_dn
|
return user_dn
|
||||||
|
|
||||||
def _populate_user_from_attributes(self):
|
def _populate_user_from_attributes(self):
|
||||||
super()._populate_user_from_attributes()
|
for field, attr in self.settings.USER_ATTR_MAP.items():
|
||||||
if not hasattr(self._user, 'email') or '@' not in self._user.email:
|
try:
|
||||||
if '@' not in self._user.username:
|
value = self.attrs[attr][0]
|
||||||
email = '{}@{}'.format(self._user.username, settings.EMAIL_SUFFIX)
|
except LookupError:
|
||||||
|
logger.warning("{} does not have a value for the attribute {}".format(self.dn, attr))
|
||||||
else:
|
else:
|
||||||
email = self._user.username
|
if not hasattr(self._user, field):
|
||||||
|
continue
|
||||||
|
if isinstance(getattr(self._user, field), bool):
|
||||||
|
value = value.lower() in ['true', '1']
|
||||||
|
setattr(self._user, field, value)
|
||||||
|
|
||||||
|
email = getattr(self._user, 'email', '')
|
||||||
|
email = construct_user_email(email, self._user.username)
|
||||||
setattr(self._user, 'email', email)
|
setattr(self._user, 'email', email)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,15 +23,12 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
# Don't need openid auth if AUTH_OPENID is False
|
# Don't need openid auth if AUTH_OPENID is False
|
||||||
if not settings.AUTH_OPENID:
|
if not settings.AUTH_OPENID:
|
||||||
logger.debug("Not settings.AUTH_OPENID")
|
|
||||||
return
|
return
|
||||||
# Don't need check single logout if user not authenticated
|
# Don't need check single logout if user not authenticated
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
logger.debug("User is not authenticated")
|
|
||||||
return
|
return
|
||||||
elif not request.session[BACKEND_SESSION_KEY].endswith(
|
elif not request.session[BACKEND_SESSION_KEY].endswith(
|
||||||
BACKEND_OPENID_AUTH_CODE):
|
BACKEND_OPENID_AUTH_CODE):
|
||||||
logger.debug("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check openid user single logout or not with access_token
|
# Check openid user single logout or not with access_token
|
||||||
|
|
|
@ -14,3 +14,7 @@ class AccessKeySerializer(serializers.ModelSerializer):
|
||||||
model = AccessKey
|
model = AccessKey
|
||||||
fields = ['id', 'secret']
|
fields = ['id', 'secret']
|
||||||
read_only_fields = ['id', 'secret']
|
read_only_fields = ['id', 'secret']
|
||||||
|
|
||||||
|
|
||||||
|
class OtpVerifySerializer(serializers.Serializer):
|
||||||
|
code = serializers.CharField(max_length=6, min_length=6)
|
||||||
|
|
|
@ -16,5 +16,6 @@ urlpatterns = [
|
||||||
path('connection-token/',
|
path('connection-token/',
|
||||||
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||||
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
||||||
|
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,18 @@
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from rest_framework.views import Response
|
|
||||||
from rest_framework import generics, serializers
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import generics, serializers
|
||||||
|
|
||||||
|
from .const import KEY_CACHE_RESOURCES_ID
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'LogTailApi', 'ResourcesIDCacheApi',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class OutputSerializer(serializers.Serializer):
|
class OutputSerializer(serializers.Serializer):
|
||||||
output = serializers.CharField()
|
output = serializers.CharField()
|
||||||
|
@ -68,3 +76,14 @@ class LogTailApi(generics.RetrieveAPIView):
|
||||||
|
|
||||||
data, end, new_mark = self.read_from_file()
|
data, end, new_mark = self.read_from_file()
|
||||||
return Response({"data": data, 'end': end, 'mark': new_mark})
|
return Response({"data": data, 'end': end, 'mark': new_mark})
|
||||||
|
|
||||||
|
|
||||||
|
class ResourcesIDCacheApi(APIView):
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
spm = str(uuid.uuid4())
|
||||||
|
resources_id = request.data.get('resources')
|
||||||
|
if resources_id:
|
||||||
|
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
|
||||||
|
cache.set(cache_key, resources_id, 300)
|
||||||
|
return Response({'spm': spm})
|
||||||
|
|
|
@ -7,3 +7,4 @@ create_success_msg = _("%(name)s was created successfully")
|
||||||
update_success_msg = _("%(name)s was updated successfully")
|
update_success_msg = _("%(name)s was updated successfully")
|
||||||
FILE_END_GUARD = ">>> Content End <<<"
|
FILE_END_GUARD = ">>> Content End <<<"
|
||||||
celery_task_pre_key = "CELERY_"
|
celery_task_pre_key = "CELERY_"
|
||||||
|
KEY_CACHE_RESOURCES_ID = "RESOURCES_ID_{}"
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.http import Http404
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
|
from rest_framework.metadata import SimpleMetadata
|
||||||
|
from rest_framework import exceptions, serializers
|
||||||
|
from rest_framework.request import clone_request
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleMetadataWithFilters(SimpleMetadata):
|
||||||
|
"""Override SimpleMetadata, adding info about filters"""
|
||||||
|
|
||||||
|
methods = {"PUT", "POST", "GET"}
|
||||||
|
attrs = [
|
||||||
|
'read_only', 'label', 'help_text',
|
||||||
|
'min_length', 'max_length',
|
||||||
|
'min_value', 'max_value', "write_only"
|
||||||
|
]
|
||||||
|
|
||||||
|
def determine_actions(self, request, view):
|
||||||
|
"""
|
||||||
|
For generic class based views we return information about
|
||||||
|
the fields that are accepted for 'PUT' and 'POST' methods.
|
||||||
|
"""
|
||||||
|
actions = {}
|
||||||
|
for method in self.methods & set(view.allowed_methods):
|
||||||
|
view.request = clone_request(request, method)
|
||||||
|
try:
|
||||||
|
# Test global permissions
|
||||||
|
if hasattr(view, 'check_permissions'):
|
||||||
|
view.check_permissions(view.request)
|
||||||
|
# Test object permissions
|
||||||
|
if method == 'PUT' and hasattr(view, 'get_object'):
|
||||||
|
view.get_object()
|
||||||
|
except (exceptions.APIException, PermissionDenied, Http404):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# If user has appropriate permissions for the view, include
|
||||||
|
# appropriate metadata about the fields that should be supplied.
|
||||||
|
serializer = view.get_serializer()
|
||||||
|
actions[method] = self.get_serializer_info(serializer)
|
||||||
|
finally:
|
||||||
|
view.request = request
|
||||||
|
return actions
|
||||||
|
|
||||||
|
def get_field_info(self, field):
|
||||||
|
"""
|
||||||
|
Given an instance of a serializer field, return a dictionary
|
||||||
|
of metadata about it.
|
||||||
|
"""
|
||||||
|
field_info = OrderedDict()
|
||||||
|
field_info['type'] = self.label_lookup[field]
|
||||||
|
field_info['required'] = getattr(field, 'required', False)
|
||||||
|
|
||||||
|
for attr in self.attrs:
|
||||||
|
value = getattr(field, attr, None)
|
||||||
|
if value is not None and value != '':
|
||||||
|
field_info[attr] = force_text(value, strings_only=True)
|
||||||
|
|
||||||
|
if getattr(field, 'child', None):
|
||||||
|
field_info['child'] = self.get_field_info(field.child)
|
||||||
|
elif getattr(field, 'fields', None):
|
||||||
|
field_info['children'] = self.get_serializer_info(field)
|
||||||
|
|
||||||
|
if (not field_info.get('read_only') and
|
||||||
|
not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
|
||||||
|
hasattr(field, 'choices')):
|
||||||
|
field_info['choices'] = [
|
||||||
|
{
|
||||||
|
'value': choice_value,
|
||||||
|
'display_name': force_text(choice_name, strings_only=True)
|
||||||
|
}
|
||||||
|
for choice_value, choice_name in field.choices.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
return field_info
|
||||||
|
|
||||||
|
def get_filters_fields(self, request, view):
|
||||||
|
fields = []
|
||||||
|
if hasattr(view, 'get_filter_fields'):
|
||||||
|
fields = view.get_filter_fields(request)
|
||||||
|
elif hasattr(view, 'filter_fields'):
|
||||||
|
fields = view.filter_fields
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_ordering_fields(self, request, view):
|
||||||
|
fields = []
|
||||||
|
if hasattr(view, 'get_ordering_fields'):
|
||||||
|
fields = view.get_filter_fields(request)
|
||||||
|
elif hasattr(view, 'ordering_fields'):
|
||||||
|
fields = view.filter_fields
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def determine_metadata(self, request, view):
|
||||||
|
metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view)
|
||||||
|
filter_fields = self.get_filters_fields(request, view)
|
||||||
|
order_fields = self.get_ordering_fields(request, view)
|
||||||
|
|
||||||
|
meta_get = metadata.get("actions", {}).get("GET", {})
|
||||||
|
for k, v in meta_get.items():
|
||||||
|
if k in filter_fields:
|
||||||
|
v["filter"] = True
|
||||||
|
if k in order_fields:
|
||||||
|
v["order"] = True
|
||||||
|
return metadata
|
|
@ -11,7 +11,7 @@ __all__ = [
|
||||||
'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin',
|
'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin',
|
||||||
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
|
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
|
||||||
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
|
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
|
||||||
'EncryptTextField', 'EncryptMixin',
|
'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
|
||||||
]
|
]
|
||||||
signer = get_signer()
|
signer = get_signer()
|
||||||
|
|
||||||
|
@ -129,4 +129,7 @@ class EncryptCharField(EncryptMixin, models.CharField):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,15 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.core.cache import cache
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.utils import html
|
from rest_framework.utils import html
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.fields import SkipField
|
from rest_framework.fields import SkipField
|
||||||
|
|
||||||
|
from .const import KEY_CACHE_RESOURCES_ID
|
||||||
|
|
||||||
|
|
||||||
class NoDeleteQuerySet(models.query.QuerySet):
|
class NoDeleteQuerySet(models.query.QuerySet):
|
||||||
|
|
||||||
|
@ -65,6 +68,27 @@ class IDInFilterMixin(object):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class IDInCacheFilterMixin(object):
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset)
|
||||||
|
spm = self.request.query_params.get('spm')
|
||||||
|
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
|
||||||
|
resources_id = cache.get(cache_key)
|
||||||
|
if resources_id and isinstance(resources_id, list):
|
||||||
|
queryset = queryset.filter(id__in=resources_id)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class IDExportFilterMixin(object):
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
# 下载导入模版
|
||||||
|
if self.request.query_params.get('template') == 'import':
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return super(IDExportFilterMixin, self).filter_queryset(queryset)
|
||||||
|
|
||||||
|
|
||||||
class BulkSerializerMixin(object):
|
class BulkSerializerMixin(object):
|
||||||
"""
|
"""
|
||||||
Become rest_framework_bulk not support uuid as a primary key
|
Become rest_framework_bulk not support uuid as a primary key
|
||||||
|
@ -131,7 +155,11 @@ class BulkListSerializerMixin(object):
|
||||||
for item in data:
|
for item in data:
|
||||||
try:
|
try:
|
||||||
# prepare child serializer to only handle one instance
|
# prepare child serializer to only handle one instance
|
||||||
|
if 'id' in item.keys():
|
||||||
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
|
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
|
||||||
|
if 'pk' in item.keys():
|
||||||
|
self.child.instance = self.instance.get(id=item['pk']) if self.instance else None
|
||||||
|
|
||||||
self.child.initial_data = item
|
self.child.initial_data = item
|
||||||
# raw
|
# raw
|
||||||
validated = self.child.run_validation(item)
|
validated = self.child.run_validation(item)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .csv import *
|
|
@ -0,0 +1,101 @@
|
||||||
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
#
|
||||||
|
|
||||||
|
import json
|
||||||
|
import unicodecsv
|
||||||
|
|
||||||
|
from rest_framework.parsers import BaseParser
|
||||||
|
from rest_framework.exceptions import ParseError
|
||||||
|
|
||||||
|
from ..utils import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class JMSCSVParser(BaseParser):
|
||||||
|
"""
|
||||||
|
Parses CSV file to serializer data
|
||||||
|
"""
|
||||||
|
|
||||||
|
media_type = 'text/csv'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _universal_newlines(stream):
|
||||||
|
"""
|
||||||
|
保证在`通用换行模式`下打开文件
|
||||||
|
"""
|
||||||
|
for line in stream.splitlines():
|
||||||
|
yield line
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _gen_rows(csv_data, charset='utf-8', **kwargs):
|
||||||
|
csv_reader = unicodecsv.reader(csv_data, encoding=charset, **kwargs)
|
||||||
|
for row in csv_reader:
|
||||||
|
if not any(row): # 空行
|
||||||
|
continue
|
||||||
|
yield row
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_fields_map(serializer):
|
||||||
|
fields_map = {}
|
||||||
|
fields = serializer.get_fields()
|
||||||
|
fields_map.update({v.label: k for k, v in fields.items()})
|
||||||
|
fields_map.update({k: k for k, _ in fields.items()})
|
||||||
|
return fields_map
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_row(row):
|
||||||
|
"""
|
||||||
|
构建json数据前的行处理
|
||||||
|
"""
|
||||||
|
_row = []
|
||||||
|
for col in row:
|
||||||
|
# 列表转换
|
||||||
|
if isinstance(col, str) and col.find("[") != -1 and col.find("]") != -1:
|
||||||
|
# 替换中文格式引号
|
||||||
|
col = col.replace("“", '"').replace("”", '"').\
|
||||||
|
replace("‘", '"').replace('’', '"').replace("'", '"')
|
||||||
|
col = json.loads(col)
|
||||||
|
_row.append(col)
|
||||||
|
return _row
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_row_data(row_data):
|
||||||
|
"""
|
||||||
|
构建json数据后的行数据处理
|
||||||
|
"""
|
||||||
|
_row_data = {}
|
||||||
|
for k, v in row_data.items():
|
||||||
|
if isinstance(v, list) \
|
||||||
|
or isinstance(v, str) and k.strip() and v.strip():
|
||||||
|
_row_data[k] = v
|
||||||
|
return _row_data
|
||||||
|
|
||||||
|
def parse(self, stream, media_type=None, parser_context=None):
|
||||||
|
parser_context = parser_context or {}
|
||||||
|
encoding = parser_context.get('encoding', 'utf-8')
|
||||||
|
try:
|
||||||
|
serializer = parser_context["view"].get_serializer()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(e, exc_info=True)
|
||||||
|
raise ParseError('The resource does not support imports!')
|
||||||
|
|
||||||
|
try:
|
||||||
|
stream_data = stream.read()
|
||||||
|
binary = self._universal_newlines(stream_data)
|
||||||
|
rows = self._gen_rows(binary, charset=encoding)
|
||||||
|
|
||||||
|
header = next(rows)
|
||||||
|
fields_map = self._get_fields_map(serializer)
|
||||||
|
header = [fields_map.get(name, '') for name in header]
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for row in rows:
|
||||||
|
row = self._process_row(row)
|
||||||
|
row_data = dict(zip(header, row))
|
||||||
|
row_data = self._process_row_data(row_data)
|
||||||
|
data.append(row_data)
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(e, exc_info=True)
|
||||||
|
raise ParseError('CSV parse error!')
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import time
|
||||||
|
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .csv import *
|
|
@ -0,0 +1,83 @@
|
||||||
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
#
|
||||||
|
|
||||||
|
import unicodecsv
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from six import BytesIO
|
||||||
|
from rest_framework.renderers import BaseRenderer
|
||||||
|
from rest_framework.utils import encoders, json
|
||||||
|
|
||||||
|
from ..utils import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class JMSCSVRender(BaseRenderer):
|
||||||
|
|
||||||
|
media_type = 'text/csv'
|
||||||
|
format = 'csv'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_header(fields, template):
|
||||||
|
if template == 'import':
|
||||||
|
header = [
|
||||||
|
k for k, v in fields.items()
|
||||||
|
if not v.read_only and k != 'org_id'
|
||||||
|
]
|
||||||
|
elif template == 'update':
|
||||||
|
header = [k for k, v in fields.items() if not v.read_only]
|
||||||
|
else:
|
||||||
|
# template in ['export']
|
||||||
|
header = [k for k, v in fields.items() if not v.write_only]
|
||||||
|
return header
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _gen_table(data, header, labels=None):
|
||||||
|
labels = labels or {}
|
||||||
|
yield [labels.get(k, k) for k in header]
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
row = [item.get(key) for key in header]
|
||||||
|
yield row
|
||||||
|
|
||||||
|
def set_response_disposition(self, serializer, context):
|
||||||
|
response = context.get('response')
|
||||||
|
if response and hasattr(serializer, 'Meta') and \
|
||||||
|
hasattr(serializer.Meta, "model"):
|
||||||
|
model_name = serializer.Meta.model.__name__.lower()
|
||||||
|
now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
|
filename = "{}_{}.csv".format(model_name, now)
|
||||||
|
disposition = 'attachment; filename="{}"'.format(filename)
|
||||||
|
response['Content-Disposition'] = disposition
|
||||||
|
|
||||||
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
|
renderer_context = renderer_context or {}
|
||||||
|
encoding = renderer_context.get('encoding', 'utf-8')
|
||||||
|
request = renderer_context['request']
|
||||||
|
template = request.query_params.get('template', 'export')
|
||||||
|
view = renderer_context['view']
|
||||||
|
data = json.loads(json.dumps(data, cls=encoders.JSONEncoder))
|
||||||
|
if template == 'import':
|
||||||
|
data = [data[0]] if data else data
|
||||||
|
|
||||||
|
try:
|
||||||
|
serializer = view.get_serializer()
|
||||||
|
self.set_response_disposition(serializer, renderer_context)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(e, exc_info=True)
|
||||||
|
value = 'The resource not support export!'.encode('utf-8')
|
||||||
|
else:
|
||||||
|
fields = serializer.get_fields()
|
||||||
|
header = self._get_header(fields, template)
|
||||||
|
labels = {k: v.label for k, v in fields.items() if v.label}
|
||||||
|
table = self._gen_table(data, header, labels)
|
||||||
|
|
||||||
|
csv_buffer = BytesIO()
|
||||||
|
csv_writer = unicodecsv.writer(csv_buffer, encoding=encoding)
|
||||||
|
for row in table:
|
||||||
|
csv_writer.writerow(row)
|
||||||
|
|
||||||
|
value = csv_buffer.getvalue()
|
||||||
|
|
||||||
|
return value
|
|
@ -0,0 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .. import api
|
||||||
|
|
||||||
|
app_name = 'common'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('resources/cache/',
|
||||||
|
api.ResourcesIDCacheApi.as_view(), name='resources-cache'),
|
||||||
|
]
|
|
@ -144,6 +144,7 @@ def is_uuid(seq):
|
||||||
|
|
||||||
def get_request_ip(request):
|
def get_request_ip(request):
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||||
|
|
||||||
if x_forwarded_for and x_forwarded_for[0]:
|
if x_forwarded_for and x_forwarded_for[0]:
|
||||||
login_ip = x_forwarded_for[0]
|
login_ip = x_forwarded_for[0]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -194,7 +194,7 @@ class Config(dict):
|
||||||
filename = os.path.join(self.root_path, filename)
|
filename = os.path.join(self.root_path, filename)
|
||||||
try:
|
try:
|
||||||
with open(filename, 'rt', encoding='utf8') as f:
|
with open(filename, 'rt', encoding='utf8') as f:
|
||||||
obj = yaml.load(f)
|
obj = yaml.safe_load(f)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
return False
|
return False
|
||||||
|
@ -273,6 +273,19 @@ class Config(dict):
|
||||||
if default_value is None:
|
if default_value is None:
|
||||||
return v
|
return v
|
||||||
tp = type(default_value)
|
tp = type(default_value)
|
||||||
|
# 对bool特殊处理
|
||||||
|
if tp is bool and isinstance(v, str):
|
||||||
|
if v in ("true", "True", "1"):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if tp in [list, dict] and isinstance(v, str):
|
||||||
|
try:
|
||||||
|
v = json.loads(v)
|
||||||
|
return v
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return v
|
||||||
|
|
||||||
try:
|
try:
|
||||||
v = tp(v)
|
v = tp(v)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -289,14 +302,10 @@ class Config(dict):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
value = None
|
value = None
|
||||||
if value is not None:
|
if value is not None:
|
||||||
return self.convert_type(item, value)
|
return value
|
||||||
# 其次从环境变量来
|
# 其次从环境变量来
|
||||||
value = os.environ.get(item, None)
|
value = os.environ.get(item, None)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if value.lower() == 'false':
|
|
||||||
value = False
|
|
||||||
elif value.lower() == 'true':
|
|
||||||
value = True
|
|
||||||
return self.convert_type(item, value)
|
return self.convert_type(item, value)
|
||||||
return self.defaults.get(item)
|
return self.defaults.get(item)
|
||||||
|
|
||||||
|
@ -343,6 +352,7 @@ defaults = {
|
||||||
'TERMINAL_SESSION_KEEP_DURATION': 9999,
|
'TERMINAL_SESSION_KEEP_DURATION': 9999,
|
||||||
'TERMINAL_HOST_KEY': '',
|
'TERMINAL_HOST_KEY': '',
|
||||||
'TERMINAL_TELNET_REGEX': '',
|
'TERMINAL_TELNET_REGEX': '',
|
||||||
|
'TERMINAL_COMMAND_STORAGE': {},
|
||||||
'SECURITY_MFA_AUTH': False,
|
'SECURITY_MFA_AUTH': False,
|
||||||
'SECURITY_LOGIN_LIMIT_COUNT': 7,
|
'SECURITY_LOGIN_LIMIT_COUNT': 7,
|
||||||
'SECURITY_LOGIN_LIMIT_TIME': 30,
|
'SECURITY_LOGIN_LIMIT_TIME': 30,
|
||||||
|
@ -361,6 +371,7 @@ defaults = {
|
||||||
'HTTP_LISTEN_PORT': 8080,
|
'HTTP_LISTEN_PORT': 8080,
|
||||||
'LOGIN_LOG_KEEP_DAYS': 90,
|
'LOGIN_LOG_KEEP_DAYS': 90,
|
||||||
'ASSETS_PERM_CACHE_TIME': 3600,
|
'ASSETS_PERM_CACHE_TIME': 3600,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
VERSION = '1.4.10'
|
VERSION = '1.5.0'
|
||||||
|
|
|
@ -67,6 +67,7 @@ INSTALLED_APPS = [
|
||||||
'terminal.apps.TerminalConfig',
|
'terminal.apps.TerminalConfig',
|
||||||
'audits.apps.AuditsConfig',
|
'audits.apps.AuditsConfig',
|
||||||
'authentication.apps.AuthenticationConfig', # authentication
|
'authentication.apps.AuthenticationConfig', # authentication
|
||||||
|
'applications.apps.ApplicationsConfig',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework_swagger',
|
'rest_framework_swagger',
|
||||||
'drf_yasg',
|
'drf_yasg',
|
||||||
|
@ -172,7 +173,7 @@ DATABASES = {
|
||||||
'OPTIONS': DB_OPTIONS
|
'OPTIONS': DB_OPTIONS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'ca.pem')
|
DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'certs', 'db_ca.pem')
|
||||||
if CONFIG.DB_ENGINE.lower() == 'mysql':
|
if CONFIG.DB_ENGINE.lower() == 'mysql':
|
||||||
DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"
|
DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"
|
||||||
if os.path.isfile(DB_CA_PATH):
|
if os.path.isfile(DB_CA_PATH):
|
||||||
|
@ -356,12 +357,30 @@ EMAIL_USE_SSL = False
|
||||||
EMAIL_USE_TLS = False
|
EMAIL_USE_TLS = False
|
||||||
EMAIL_SUBJECT_PREFIX = '[JMS] '
|
EMAIL_SUBJECT_PREFIX = '[JMS] '
|
||||||
|
|
||||||
|
#Email custom content
|
||||||
|
EMAIL_CUSTOM_USER_CREATED_SUBJECT = ''
|
||||||
|
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = ''
|
||||||
|
EMAIL_CUSTOM_USER_CREATED_BODY = ''
|
||||||
|
EMAIL_CUSTOM_USER_CREATED_SIGNATURE = ''
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
# Use Django's standard `django.contrib.auth` permissions,
|
# Use Django's standard `django.contrib.auth` permissions,
|
||||||
# or allow read-only access for unauthenticated users.
|
# or allow read-only access for unauthenticated users.
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
'common.permissions.IsOrgAdmin',
|
'common.permissions.IsOrgAdmin',
|
||||||
),
|
),
|
||||||
|
'DEFAULT_RENDERER_CLASSES': (
|
||||||
|
'rest_framework.renderers.JSONRenderer',
|
||||||
|
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||||
|
'common.renders.JMSCSVRender',
|
||||||
|
),
|
||||||
|
'DEFAULT_PARSER_CLASSES': (
|
||||||
|
'rest_framework.parsers.JSONParser',
|
||||||
|
'rest_framework.parsers.FormParser',
|
||||||
|
'rest_framework.parsers.MultiPartParser',
|
||||||
|
'common.parsers.JMSCSVParser',
|
||||||
|
'rest_framework.parsers.FileUploadParser',
|
||||||
|
),
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
# 'rest_framework.authentication.BasicAuthentication',
|
# 'rest_framework.authentication.BasicAuthentication',
|
||||||
'authentication.backends.api.AccessKeyAuthentication',
|
'authentication.backends.api.AccessKeyAuthentication',
|
||||||
|
@ -374,6 +393,7 @@ REST_FRAMEWORK = {
|
||||||
'rest_framework.filters.SearchFilter',
|
'rest_framework.filters.SearchFilter',
|
||||||
'rest_framework.filters.OrderingFilter',
|
'rest_framework.filters.OrderingFilter',
|
||||||
),
|
),
|
||||||
|
'DEFAULT_METADATA_CLASS': 'common.drfmetadata.SimpleMetadataWithFilters',
|
||||||
'ORDERING_PARAM': "order",
|
'ORDERING_PARAM': "order",
|
||||||
'SEARCH_PARAM': "search",
|
'SEARCH_PARAM': "search",
|
||||||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
||||||
|
@ -406,6 +426,12 @@ AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org'
|
||||||
AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)'
|
AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)'
|
||||||
AUTH_LDAP_START_TLS = False
|
AUTH_LDAP_START_TLS = False
|
||||||
AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"}
|
AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"}
|
||||||
|
AUTH_LDAP_GLOBAL_OPTIONS = {
|
||||||
|
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
|
||||||
|
}
|
||||||
|
LDAP_CERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ca.pem")
|
||||||
|
if os.path.isfile(LDAP_CERT_FILE):
|
||||||
|
AUTH_LDAP_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CACERTFILE] = LDAP_CERT_FILE
|
||||||
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||||
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||||
|
@ -507,12 +533,7 @@ DEFAULT_TERMINAL_COMMAND_STORAGE = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
TERMINAL_COMMAND_STORAGE = {
|
TERMINAL_COMMAND_STORAGE = CONFIG.TERMINAL_COMMAND_STORAGE
|
||||||
# 'ali-es': {
|
|
||||||
# 'TYPE': 'elasticsearch',
|
|
||||||
# 'HOSTS': ['http://elastic:changeme@localhost:9200'],
|
|
||||||
# },
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_TERMINAL_REPLAY_STORAGE = {
|
DEFAULT_TERMINAL_REPLAY_STORAGE = {
|
||||||
"default": {
|
"default": {
|
||||||
|
|
|
@ -20,6 +20,8 @@ api_v1 = [
|
||||||
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
||||||
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
|
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
|
||||||
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
|
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
|
||||||
|
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
||||||
|
path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')),
|
||||||
]
|
]
|
||||||
|
|
||||||
api_v2 = [
|
api_v2 = [
|
||||||
|
@ -37,6 +39,7 @@ app_view_patterns = [
|
||||||
path('audits/', include('audits.urls.view_urls', namespace='audits')),
|
path('audits/', include('audits.urls.view_urls', namespace='audits')),
|
||||||
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
|
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
|
||||||
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
||||||
|
path('applications/', include('applications.urls.views_urls', namespace='applications')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-11-21 19:14+0800\n"
|
"POT-Creation-Date: 2019-05-27 15:53+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -17,58 +17,58 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:168
|
#: static/js/jumpserver.js:249
|
||||||
msgid "Update is successful!"
|
msgid "Update is successful!"
|
||||||
msgstr "更新成功"
|
msgstr "更新成功"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:170
|
#: static/js/jumpserver.js:251
|
||||||
msgid "An unknown error occurred while updating.."
|
msgid "An unknown error occurred while updating.."
|
||||||
msgstr "更新时发生未知错误"
|
msgstr "更新时发生未知错误"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:236 static/js/jumpserver.js:273
|
#: static/js/jumpserver.js:315 static/js/jumpserver.js:352
|
||||||
#: static/js/jumpserver.js:276
|
#: static/js/jumpserver.js:355
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "错误"
|
msgstr "错误"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:236
|
#: static/js/jumpserver.js:315
|
||||||
msgid "Being used by the asset, please unbind the asset first."
|
msgid "Being used by the asset, please unbind the asset first."
|
||||||
msgstr "正在被资产使用中,请先解除资产绑定"
|
msgstr "正在被资产使用中,请先解除资产绑定"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:242 static/js/jumpserver.js:283
|
#: static/js/jumpserver.js:321 static/js/jumpserver.js:362
|
||||||
msgid "Delete the success"
|
msgid "Delete the success"
|
||||||
msgstr "删除成功"
|
msgstr "删除成功"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:248
|
#: static/js/jumpserver.js:327
|
||||||
msgid "Are you sure about deleting it?"
|
msgid "Are you sure about deleting it?"
|
||||||
msgstr "你确定删除吗 ?"
|
msgstr "你确定删除吗 ?"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:252 static/js/jumpserver.js:293
|
#: static/js/jumpserver.js:331 static/js/jumpserver.js:372
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "取消"
|
msgstr "取消"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:254 static/js/jumpserver.js:295
|
#: static/js/jumpserver.js:333 static/js/jumpserver.js:374
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "确认"
|
msgstr "确认"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:273
|
#: static/js/jumpserver.js:352
|
||||||
msgid ""
|
msgid ""
|
||||||
"The organization contains undeleted information. Please try again after "
|
"The organization contains undeleted information. Please try again after "
|
||||||
"deleting"
|
"deleting"
|
||||||
msgstr "组织中包含未删除信息,请删除后重试"
|
msgstr "组织中包含未删除信息,请删除后重试"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:276
|
#: static/js/jumpserver.js:355
|
||||||
msgid ""
|
msgid ""
|
||||||
"Do not perform this operation under this organization. Try again after "
|
"Do not perform this operation under this organization. Try again after "
|
||||||
"switching to another organization"
|
"switching to another organization"
|
||||||
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
|
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:289
|
#: static/js/jumpserver.js:368
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please ensure that the following information in the organization has been "
|
"Please ensure that the following information in the organization has been "
|
||||||
"deleted"
|
"deleted"
|
||||||
msgstr "请确保组织内的以下信息已删除"
|
msgstr "请确保组织内的以下信息已删除"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:290
|
#: static/js/jumpserver.js:369
|
||||||
msgid ""
|
msgid ""
|
||||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||||
"Labels、Asset permission"
|
"Labels、Asset permission"
|
||||||
|
@ -76,52 +76,76 @@ msgstr ""
|
||||||
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
|
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
|
||||||
"规则"
|
"规则"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:329
|
#: static/js/jumpserver.js:408
|
||||||
msgid "Loading ..."
|
msgid "Loading ..."
|
||||||
msgstr "加载中 ..."
|
msgstr "加载中 ..."
|
||||||
|
|
||||||
#: static/js/jumpserver.js:330
|
#: static/js/jumpserver.js:409
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "搜索"
|
msgstr "搜索"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:333
|
#: static/js/jumpserver.js:412
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgid "Selected item %d"
|
msgid "Selected item %d"
|
||||||
msgstr "选中 %d 项"
|
msgstr "选中 %d 项"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:337
|
#: static/js/jumpserver.js:416
|
||||||
msgid "Per page _MENU_"
|
msgid "Per page _MENU_"
|
||||||
msgstr "每页 _MENU_"
|
msgstr "每页 _MENU_"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:338
|
#: static/js/jumpserver.js:417
|
||||||
msgid ""
|
msgid ""
|
||||||
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
|
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
|
||||||
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
|
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:341
|
#: static/js/jumpserver.js:420
|
||||||
msgid "No match"
|
msgid "No match"
|
||||||
msgstr "没有匹配项"
|
msgstr "没有匹配项"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:342
|
#: static/js/jumpserver.js:421
|
||||||
msgid "No record"
|
msgid "No record"
|
||||||
msgstr "没有记录"
|
msgstr "没有记录"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:701
|
#: static/js/jumpserver.js:563
|
||||||
|
msgid "Unknown error occur"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:800
|
||||||
msgid "Password minimum length {N} bits"
|
msgid "Password minimum length {N} bits"
|
||||||
msgstr "密码最小长度 {N} 位"
|
msgstr "密码最小长度 {N} 位"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:702
|
#: static/js/jumpserver.js:801
|
||||||
msgid "Must contain capital letters"
|
msgid "Must contain capital letters"
|
||||||
msgstr "必须包含大写字母"
|
msgstr "必须包含大写字母"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:703
|
#: static/js/jumpserver.js:802
|
||||||
msgid "Must contain lowercase letters"
|
msgid "Must contain lowercase letters"
|
||||||
msgstr "必须包含小写字母"
|
msgstr "必须包含小写字母"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:704
|
#: static/js/jumpserver.js:803
|
||||||
msgid "Must contain numeric characters"
|
msgid "Must contain numeric characters"
|
||||||
msgstr "必须包含数字字符"
|
msgstr "必须包含数字字符"
|
||||||
|
|
||||||
#: static/js/jumpserver.js:705
|
#: static/js/jumpserver.js:804
|
||||||
msgid "Must contain special characters"
|
msgid "Must contain special characters"
|
||||||
msgstr "必须包含特殊字符"
|
msgstr "必须包含特殊字符"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:976
|
||||||
|
msgid "Export failed"
|
||||||
|
msgstr "导出失败"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:993
|
||||||
|
msgid "Import Success"
|
||||||
|
msgstr "导入成功"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:998
|
||||||
|
msgid "Update Success"
|
||||||
|
msgstr "更新成功"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:1028
|
||||||
|
msgid "Import failed"
|
||||||
|
msgstr "导入失败"
|
||||||
|
|
||||||
|
#: static/js/jumpserver.js:1033
|
||||||
|
msgid "Update failed"
|
||||||
|
msgstr "更新失败"
|
||||||
|
|
|
@ -124,6 +124,9 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
|
||||||
def display_ok_hosts(self):
|
def display_ok_hosts(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def display_failed_stderr(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CommandResultCallback(AdHocResultCallback):
|
class CommandResultCallback(AdHocResultCallback):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from ansible import context
|
||||||
|
from ansible.module_utils.common.collections import ImmutableDict
|
||||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||||
from ansible.vars.manager import VariableManager
|
from ansible.vars.manager import VariableManager
|
||||||
from ansible.parsing.dataloader import DataLoader
|
from ansible.parsing.dataloader import DataLoader
|
||||||
|
@ -33,29 +36,18 @@ Options = namedtuple('Options', [
|
||||||
|
|
||||||
|
|
||||||
def get_default_options():
|
def get_default_options():
|
||||||
options = Options(
|
options = dict(
|
||||||
listtags=False,
|
|
||||||
listtasks=False,
|
|
||||||
listhosts=False,
|
|
||||||
syntax=False,
|
syntax=False,
|
||||||
timeout=30,
|
timeout=30,
|
||||||
connection='ssh',
|
connection='ssh',
|
||||||
module_path='',
|
|
||||||
forks=10,
|
forks=10,
|
||||||
remote_user='root',
|
remote_user='root',
|
||||||
private_key_file=None,
|
private_key_file=None,
|
||||||
ssh_common_args="",
|
|
||||||
ssh_extra_args="",
|
|
||||||
sftp_extra_args="",
|
|
||||||
scp_extra_args="",
|
|
||||||
become=None,
|
become=None,
|
||||||
become_method=None,
|
become_method=None,
|
||||||
become_user=None,
|
become_user=None,
|
||||||
verbosity=None,
|
verbosity=1,
|
||||||
extra_vars=[],
|
|
||||||
check=False,
|
check=False,
|
||||||
playbook_path='/etc/ansible/',
|
|
||||||
passwords=None,
|
|
||||||
diff=False,
|
diff=False,
|
||||||
gathering='implicit',
|
gathering='implicit',
|
||||||
remote_tmp='/tmp/.ansible'
|
remote_tmp='/tmp/.ansible'
|
||||||
|
@ -108,9 +100,9 @@ class PlayBookRunner:
|
||||||
inventory=self.inventory,
|
inventory=self.inventory,
|
||||||
variable_manager=self.variable_manager,
|
variable_manager=self.variable_manager,
|
||||||
loader=self.loader,
|
loader=self.loader,
|
||||||
options=self.options,
|
passwords={"conn_pass": self.passwords}
|
||||||
passwords=self.passwords
|
|
||||||
)
|
)
|
||||||
|
context.CLIARGS = ImmutableDict(self.options)
|
||||||
|
|
||||||
if executor._tqm:
|
if executor._tqm:
|
||||||
executor._tqm._stdout_callback = self.results_callback
|
executor._tqm._stdout_callback = self.results_callback
|
||||||
|
@ -185,11 +177,10 @@ class AdHocRunner:
|
||||||
return cleaned_tasks
|
return cleaned_tasks
|
||||||
|
|
||||||
def update_options(self, options):
|
def update_options(self, options):
|
||||||
|
_options = {k: v for k, v in self.default_options.items()}
|
||||||
if options and isinstance(options, dict):
|
if options and isinstance(options, dict):
|
||||||
options = self.__class__.default_options._replace(**options)
|
_options.update(options)
|
||||||
else:
|
return _options
|
||||||
options = self.__class__.default_options
|
|
||||||
return options
|
|
||||||
|
|
||||||
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
|
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
|
||||||
"""
|
"""
|
||||||
|
@ -202,6 +193,7 @@ class AdHocRunner:
|
||||||
self.check_pattern(pattern)
|
self.check_pattern(pattern)
|
||||||
self.results_callback = self.get_result_callback()
|
self.results_callback = self.get_result_callback()
|
||||||
cleaned_tasks = self.clean_tasks(tasks)
|
cleaned_tasks = self.clean_tasks(tasks)
|
||||||
|
context.CLIARGS = ImmutableDict(self.options)
|
||||||
|
|
||||||
play_source = dict(
|
play_source = dict(
|
||||||
name=play_name,
|
name=play_name,
|
||||||
|
@ -220,9 +212,8 @@ class AdHocRunner:
|
||||||
inventory=self.inventory,
|
inventory=self.inventory,
|
||||||
variable_manager=self.variable_manager,
|
variable_manager=self.variable_manager,
|
||||||
loader=self.loader,
|
loader=self.loader,
|
||||||
options=self.options,
|
|
||||||
stdout_callback=self.results_callback,
|
stdout_callback=self.results_callback,
|
||||||
passwords=self.options.passwords,
|
passwords={"conn_pass": self.options.get("password", "")}
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
tqm.run(play)
|
tqm.run(play)
|
||||||
|
@ -230,8 +221,9 @@ class AdHocRunner:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise AnsibleError(e)
|
raise AnsibleError(e)
|
||||||
finally:
|
finally:
|
||||||
|
if tqm is not None:
|
||||||
tqm.cleanup()
|
tqm.cleanup()
|
||||||
self.loader.cleanup_all_tmp_files()
|
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
|
||||||
|
|
||||||
|
|
||||||
class CommandRunner(AdHocRunner):
|
class CommandRunner(AdHocRunner):
|
||||||
|
|
|
@ -15,7 +15,7 @@ class TestAdHocRunner(unittest.TestCase):
|
||||||
host_data = [
|
host_data = [
|
||||||
{
|
{
|
||||||
"hostname": "testserver",
|
"hostname": "testserver",
|
||||||
"ip": "192.168.244.168",
|
"ip": "192.168.244.185",
|
||||||
"port": 22,
|
"port": 22,
|
||||||
"username": "root",
|
"username": "root",
|
||||||
"password": "redhat",
|
"password": "redhat",
|
||||||
|
|
|
@ -35,7 +35,6 @@ class JMSBaseInventory(BaseInventory):
|
||||||
info["vars"].update({
|
info["vars"].update({
|
||||||
label.name: label.value
|
label.name: label.value
|
||||||
})
|
})
|
||||||
info["groups"].append("{}:{}".format(label.name, label.value))
|
|
||||||
if asset.domain:
|
if asset.domain:
|
||||||
info["vars"].update({
|
info["vars"].update({
|
||||||
"domain": asset.domain.name,
|
"domain": asset.domain.name,
|
||||||
|
|
|
@ -8,9 +8,12 @@ from django.shortcuts import redirect, get_object_or_404
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from django.http.response import HttpResponseForbidden
|
from django.http.response import HttpResponseForbidden
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .utils import current_org, set_current_org, set_to_root_org
|
from .utils import (
|
||||||
|
current_org, set_current_org, set_to_root_org, get_current_org_id
|
||||||
|
)
|
||||||
from .models import Organization
|
from .models import Organization
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
@ -18,7 +21,8 @@ tl = Local()
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
|
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
|
||||||
'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'OrgMembershipModelViewSetMixin'
|
'RootOrgViewMixin', 'OrgMembershipSerializerMixin',
|
||||||
|
'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -202,3 +206,11 @@ class OrgMembershipModelViewSetMixin:
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = self.membership_class.objects.filter(organization=self.org)
|
queryset = self.membership_class.objects.filter(organization=self.org)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class OrgResourceSerializerMixin(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id
|
||||||
|
(同时为serializer.is_valid()对Model的unique_together校验做准备)
|
||||||
|
"""
|
||||||
|
org_id = serializers.HiddenField(default=get_current_org_id)
|
||||||
|
|
|
@ -38,4 +38,10 @@ def get_current_org():
|
||||||
return _find('current_org')
|
return _find('current_org')
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_org_id():
|
||||||
|
org = get_current_org()
|
||||||
|
org_id = str(org.id) if org.is_real() else ''
|
||||||
|
return org_id
|
||||||
|
|
||||||
|
|
||||||
current_org = LocalProxy(partial(_find, 'current_org'))
|
current_org = LocalProxy(partial(_find, 'current_org'))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from .permission import *
|
from .asset_permission import *
|
||||||
from .user_permission import *
|
from .user_permission import *
|
||||||
from .user_group_permission import *
|
from .user_group_permission import *
|
||||||
|
from .remote_app_permission import *
|
||||||
|
|
|
@ -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})
|
||||||
|
|
|
@ -10,10 +10,12 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||||
from common.tree import TreeNodeSerializer
|
from common.tree import TreeNodeSerializer
|
||||||
from orgs.utils import set_to_root_org
|
from orgs.utils import set_to_root_org
|
||||||
from ..utils import (
|
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 (
|
from ..hands import (
|
||||||
AssetGrantedSerializer, UserGroup, Node, NodeSerializer
|
AssetGrantedSerializer, UserGroup, Node, NodeSerializer,
|
||||||
|
RemoteAppSerializer,
|
||||||
)
|
)
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
|
||||||
|
@ -22,6 +24,7 @@ __all__ = [
|
||||||
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
|
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
|
||||||
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
|
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
|
||||||
'UserGroupGrantedNodesWithAssetsAsTreeApi',
|
'UserGroupGrantedNodesWithAssetsAsTreeApi',
|
||||||
|
'UserGroupGrantedRemoteAppsApi',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,3 +141,20 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
|
||||||
for asset, system_users in assets.items():
|
for asset, system_users in assets.items():
|
||||||
asset.system_users_granted = system_users
|
asset.system_users_granted = system_users
|
||||||
return assets
|
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
|
||||||
|
|
|
@ -17,14 +17,15 @@ from common.utils import get_logger
|
||||||
from orgs.utils import set_to_root_org
|
from orgs.utils import set_to_root_org
|
||||||
from ..utils import (
|
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,
|
||||||
check_system_user_action
|
check_system_user_action, RemoteAppPermissionUtil,
|
||||||
|
construct_remote_apps_tree_root, parse_remote_app_to_tree_node,
|
||||||
)
|
)
|
||||||
from ..hands import (
|
from ..hands import (
|
||||||
AssetGrantedSerializer, User, Asset, Node,
|
User, Asset, Node, SystemUser, RemoteApp, AssetGrantedSerializer,
|
||||||
SystemUser, NodeSerializer
|
NodeSerializer, RemoteAppSerializer,
|
||||||
)
|
)
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..mixins import AssetsFilterMixin
|
from ..mixins import AssetsFilterMixin, RemoteAppFilterMixin
|
||||||
from ..models import Action
|
from ..models import Action
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -34,6 +35,8 @@ __all__ = [
|
||||||
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
|
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
|
||||||
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
|
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
|
||||||
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
|
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
|
||||||
|
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
|
||||||
|
'UserGrantedRemoteAppsAsTreeApi',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -447,3 +450,79 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
|
||||||
|
|
||||||
actions = [action.name for action in getattr(_su, 'actions', [])]
|
actions = [action.name for action in getattr(_su, 'actions', [])]
|
||||||
return Response({'actions': actions}, status=200)
|
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):
|
||||||
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from .asset_permission import *
|
||||||
|
from .remote_app_permission import *
|
|
@ -6,9 +6,13 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins import OrgModelForm
|
from orgs.mixins import OrgModelForm
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from .models import AssetPermission
|
from perms.models import AssetPermission
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AssetPermissionForm',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionForm(OrgModelForm):
|
class AssetPermissionForm(OrgModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
|
@ -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']
|
|
@ -3,8 +3,11 @@
|
||||||
|
|
||||||
from common.permissions import AdminUserRequiredMixin
|
from common.permissions import AdminUserRequiredMixin
|
||||||
from users.models import User, UserGroup
|
from users.models import User, UserGroup
|
||||||
from assets.models import Asset, SystemUser, Node
|
from assets.models import Asset, SystemUser, Node, RemoteApp
|
||||||
from assets.serializers import AssetGrantedSerializer, NodeSerializer
|
from assets.serializers import (
|
||||||
|
AssetGrantedSerializer, NodeSerializer
|
||||||
|
)
|
||||||
|
from applications.serializers import RemoteAppSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-05-21 08:19
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
('users', '0019_auto_20190304_1459'),
|
||||||
|
('applications', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('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='granted_by_permissions', to='applications.RemoteApp', verbose_name='RemoteApp')),
|
||||||
|
('user_groups', models.ManyToManyField(blank=True, to='users.UserGroup', verbose_name='User group')),
|
||||||
|
('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'RemoteApp permission',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='assetpermission',
|
||||||
|
name='user_groups',
|
||||||
|
field=models.ManyToManyField(blank=True, to='users.UserGroup', verbose_name='User group'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='assetpermission',
|
||||||
|
name='users',
|
||||||
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='remoteapppermission',
|
||||||
|
unique_together={('org_id', 'name')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,6 +2,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AssetsFilterMixin', 'RemoteAppFilterMixin',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class AssetsFilterMixin(object):
|
class AssetsFilterMixin(object):
|
||||||
"""
|
"""
|
||||||
对资产进行过滤(查询,排序)
|
对资产进行过滤(查询,排序)
|
||||||
|
@ -34,3 +39,38 @@ class AssetsFilterMixin(object):
|
||||||
|
|
||||||
queryset = sort_assets(queryset, order_by=order_by, reverse=reverse)
|
queryset = sort_assets(queryset, order_by=order_by, reverse=reverse)
|
||||||
return queryset
|
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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from .asset_permission import *
|
||||||
|
from .remote_app_permission import *
|
|
@ -2,12 +2,17 @@ import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from common.utils import date_expired_default, set_or_append_attr_bulk
|
from common.utils import date_expired_default, set_or_append_attr_bulk
|
||||||
from orgs.mixins import OrgModelMixin, OrgManager
|
from orgs.mixins import OrgModelMixin
|
||||||
|
|
||||||
from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
|
from ..const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
|
||||||
|
from .base import BasePermission
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Action', 'AssetPermission', 'NodePermission',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Action(models.Model):
|
class Action(models.Model):
|
||||||
|
@ -28,69 +33,16 @@ class Action(models.Model):
|
||||||
return cls.objects.get(name=PERMS_ACTION_NAME_ALL)
|
return cls.objects.get(name=PERMS_ACTION_NAME_ALL)
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionQuerySet(models.QuerySet):
|
class AssetPermission(BasePermission):
|
||||||
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 AssetPermissionManager(OrgManager):
|
|
||||||
def valid(self):
|
|
||||||
return self.get_queryset().valid()
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermission(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='asset_permissions', blank=True, verbose_name=_("User"))
|
|
||||||
user_groups = models.ManyToManyField('users.UserGroup', related_name='asset_permissions', blank=True, verbose_name=_("User group"))
|
|
||||||
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
|
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
|
||||||
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
|
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
|
||||||
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
|
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
|
||||||
actions = models.ManyToManyField('Action', related_name='permissions', blank=True, verbose_name=_('Action'))
|
actions = models.ManyToManyField('Action', related_name='permissions', blank=True, verbose_name=_('Action'))
|
||||||
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 = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [('org_id', 'name')]
|
unique_together = [('org_id', 'name')]
|
||||||
verbose_name = _("Asset permission")
|
verbose_name = _("Asset permission")
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id_str(self):
|
|
||||||
return str(self.id)
|
|
||||||
|
|
||||||
@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_assets(self):
|
def get_all_assets(self):
|
||||||
assets = set(self.assets.all())
|
assets = set(self.assets.all())
|
||||||
for node in self.nodes.all():
|
for node in self.nodes.all():
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue