mirror of https://github.com/jumpserver/jumpserver
commit
d46f5858f8
|
@ -6,7 +6,8 @@ RUN useradd jumpserver
|
|||
|
||||
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 && pip install --upgrade pip setuptools && \
|
||||
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.pagination import LimitOffsetPagination
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsOrgAdmin
|
||||
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
|
||||
"""
|
||||
|
|
|
@ -16,8 +16,9 @@ from django.urls import reverse_lazy
|
|||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||
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.
|
||||
"""
|
||||
|
@ -47,6 +48,19 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
|||
pagination_class = LimitOffsetPagination
|
||||
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):
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
if not node_id:
|
||||
|
@ -89,7 +103,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
|||
return queryset
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
Asset bulk update api
|
||||
"""
|
||||
|
|
|
@ -39,6 +39,8 @@ __all__ = [
|
|||
|
||||
|
||||
class NodeViewSet(viewsets.ModelViewSet):
|
||||
filter_fields = ('value', 'key', )
|
||||
search_fields = filter_fields
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
|
|
@ -21,6 +21,7 @@ from rest_framework.pagination import LimitOffsetPagination
|
|||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
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
|
||||
"""
|
||||
|
|
|
@ -11,7 +11,7 @@ class AuthBookBackend(BaseBackend):
|
|||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
queryset = AuthBook.objects.all()
|
||||
if username:
|
||||
if username is not None:
|
||||
queryset = queryset.filter(username=username)
|
||||
if asset:
|
||||
queryset = queryset.filter(asset=asset)
|
||||
|
|
|
@ -27,7 +27,7 @@ class AdminUserBackend(BaseBackend):
|
|||
instances = []
|
||||
assets = cls._get_assets(asset)
|
||||
for asset in assets:
|
||||
if username and asset.admin_user.username != username:
|
||||
if username is not None and asset.admin_user.username != username:
|
||||
continue
|
||||
instance = construct_authbook_object(asset.admin_user, asset)
|
||||
instances.append(instance)
|
||||
|
|
|
@ -30,7 +30,7 @@ class SystemUserBackend(BaseBackend):
|
|||
@classmethod
|
||||
def _filter_system_users_by_username(cls, system_users, username):
|
||||
_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]
|
||||
return _system_users
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
UPDATE_ASSETS_HARDWARE_TASKS = [
|
||||
{
|
||||
'name': "setup",
|
||||
|
@ -51,3 +54,4 @@ TASK_OPTIONS = {
|
|||
|
||||
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
|
||||
|
||||
|
||||
|
|
|
@ -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 .utils 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)
|
||||
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'))
|
||||
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
|
|
|
@ -78,7 +78,7 @@ class AuthBook(AssetUser):
|
|||
if host == self.asset.hostname:
|
||||
_connectivity = self.UNREACHABLE
|
||||
|
||||
for host in value.get('contacted', {}).keys():
|
||||
for host in value.get('contacted', []):
|
||||
if host == self.asset.hostname:
|
||||
_connectivity = self.REACHABLE
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
@ -15,14 +16,29 @@ class AdminUserSerializer(serializers.ModelSerializer):
|
|||
"""
|
||||
管理用户
|
||||
"""
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
unreachable_amount = serializers.SerializerMethodField()
|
||||
reachable_amount = serializers.SerializerMethodField()
|
||||
password = serializers.CharField(
|
||||
required=False, write_only=True, label=_('Password')
|
||||
)
|
||||
unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable'))
|
||||
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
|
||||
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
|
||||
|
||||
class Meta:
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
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):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
#
|
||||
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.serializers import AdaptedBulkListSerializer
|
||||
from ..models import Asset
|
||||
|
@ -13,15 +16,35 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin):
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
class Meta:
|
||||
model = Asset
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = '__all__'
|
||||
validators = []
|
||||
# validators = [] # 解决批量导入时unique_together字段校验失败
|
||||
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
|
||||
def setup_eager_loading(cls, queryset):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from ..models import SystemUser, Asset
|
||||
|
@ -10,16 +12,36 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
"""
|
||||
系统用户
|
||||
"""
|
||||
unreachable_amount = serializers.SerializerMethodField()
|
||||
reachable_amount = serializers.SerializerMethodField()
|
||||
unreachable_assets = serializers.SerializerMethodField()
|
||||
reachable_assets = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
password = serializers.CharField(
|
||||
required=False, write_only=True, label=_('Password')
|
||||
)
|
||||
unreachable_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:
|
||||
model = SystemUser
|
||||
exclude = ('_password', '_private_key', '_public_key')
|
||||
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):
|
||||
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 %}
|
||||
{% block modal_id %}asset_import_modal{% endblock %}
|
||||
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
|
||||
{% 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 %}
|
||||
|
||||
{% block modal_title%}{% trans "Import assets" %}{% endblock %}
|
||||
|
||||
{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% 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>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
@ -112,9 +113,12 @@ function initTable() {
|
|||
}
|
||||
}},
|
||||
{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 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(test_btn + update_auth_btn);
|
||||
btn += view_btn;
|
||||
btn += test_btn;
|
||||
$(td).html(btn);
|
||||
}}
|
||||
],
|
||||
|
||||
|
@ -201,5 +205,11 @@ $(document).ready(function () {
|
|||
$('#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>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
|
||||
|
@ -12,6 +9,30 @@
|
|||
{% trans 'You can set any one for Windows or other hardware.' %}
|
||||
</div>
|
||||
{% 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 %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
|
@ -36,11 +57,14 @@
|
|||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_admin_user_import_modal.html' %}
|
||||
{% include 'assets/_admin_user_update_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var admin_user_table = 0;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#admin_user_list_table'),
|
||||
columnDefs: [
|
||||
|
@ -93,7 +117,12 @@ $(document).ready(function(){
|
|||
columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
|
||||
{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 () {
|
||||
|
@ -107,6 +136,70 @@ $(document).ready(function(){
|
|||
$data_table.ajax.reload();
|
||||
}, 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>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
{% load common_tags %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
|
@ -87,6 +83,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
@ -117,14 +114,14 @@ function initAssetUserTable() {
|
|||
$(td).html(cellData.slice(0, -6));
|
||||
}},
|
||||
{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);
|
||||
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);
|
||||
btn += view_btn;
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
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);
|
||||
{% else %}
|
||||
$(td).html(update_auth_btn);
|
||||
btn += test_btn;
|
||||
{% 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 () {
|
||||
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() {
|
||||
username = $(this).data('username');
|
||||
var hostname = "{{ asset.hostname }}";
|
||||
|
@ -214,5 +200,11 @@ $(document).ready(function () {
|
|||
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>
|
||||
{% endblock %}
|
|
@ -67,14 +67,26 @@
|
|||
</div>
|
||||
<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="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
<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>
|
||||
<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>#}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% include 'assets/_asset_update_modal.html' %}
|
||||
{% include 'assets/_asset_import_modal.html' %}
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
{% endblock %}
|
||||
|
@ -457,49 +469,78 @@ $(document).ready(function(){
|
|||
asset_table.search(val).draw();
|
||||
})
|
||||
.on('click', '.btn_export', function () {
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var rows = $data_table.rows('.selected').data();
|
||||
|
||||
var assets = [];
|
||||
$.each(rows, function (index, obj) {
|
||||
assets.push(obj.id)
|
||||
});
|
||||
$.ajax({
|
||||
url: "{% url "assets:asset-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({assets_id: assets, node_id: current_node_id}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Export failed');
|
||||
var assets = asset_table.selected;
|
||||
var data = {
|
||||
'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',
|
||||
params: {
|
||||
search: search,
|
||||
node_id: current_node_id || ''
|
||||
}
|
||||
})
|
||||
};
|
||||
APIExportData(props);
|
||||
})
|
||||
.on('click', '#btn_asset_import', function () {
|
||||
var $form = $('#fm_asset_import');
|
||||
var action = $form.attr("action");
|
||||
.on('click', '#btn_import_confirm', function () {
|
||||
var file = document.getElementById('id_file').files[0];
|
||||
if(!file){
|
||||
toastr.error("{% trans "Please select file" %}");
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:asset-list' %}";
|
||||
if (current_node_id){
|
||||
action = setUrlParam(action, 'node_id', current_node_id);
|
||||
$form.attr("action", action)
|
||||
url = setUrlParam(url, 'node_id', current_node_id);
|
||||
}
|
||||
$form.find('.help-block').remove();
|
||||
function success (data) {
|
||||
if (data.valid === false) {
|
||||
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
|
||||
} else {
|
||||
$('#id_created').html(data.created_info);
|
||||
$('#id_created_detail').html(data.created.join(', '));
|
||||
$('#id_updated').html(data.updated_info);
|
||||
$('#id_updated_detail').html(data.updated.join(', '));
|
||||
$('#id_failed').html(data.failed_info);
|
||||
$('#id_failed_detail').html(data.failed.join(', '));
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
$data_table.ajax.reload();
|
||||
var data_table = $('#asset_list_table').DataTable();
|
||||
|
||||
APIImportData({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: file,
|
||||
data_table: data_table
|
||||
});
|
||||
})
|
||||
.on('click', '#download_update_template', function () {
|
||||
var assets = asset_table.selected;
|
||||
var data = {
|
||||
'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 () {
|
||||
var url = "{% url 'assets:asset-create' %}";
|
||||
|
@ -584,15 +625,17 @@ $(document).ready(function(){
|
|||
})
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push(this.data().id);
|
||||
});
|
||||
var id_list = asset_table.selected;
|
||||
if (id_list.length === 0) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
||||
var data = {
|
||||
'resources': id_list
|
||||
};
|
||||
function refreshTag() {
|
||||
$('#asset_list_table').DataTable().ajax.reload();
|
||||
}
|
||||
|
||||
function doDeactive() {
|
||||
var data = [];
|
||||
|
@ -601,7 +644,8 @@ $(document).ready(function(){
|
|||
data.push(obj);
|
||||
});
|
||||
function success() {
|
||||
asset_table.ajax.reload()
|
||||
setTimeout( function () {
|
||||
window.location.reload();}, 500);
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -617,7 +661,8 @@ $(document).ready(function(){
|
|||
data.push(obj);
|
||||
});
|
||||
function success() {
|
||||
asset_table.ajax.reload()
|
||||
setTimeout( function () {
|
||||
window.location.reload();}, 300);
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -636,68 +681,72 @@ $(document).ready(function(){
|
|||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
},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.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||
$('#asset_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
}
|
||||
function fail() {
|
||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||
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;
|
||||
});
|
||||
url: "{% url 'api-common:resources-cache' %}",
|
||||
method:'POST',
|
||||
body:JSON.stringify(data),
|
||||
success:success,
|
||||
error:fail
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function doUpdate() {
|
||||
var data = {
|
||||
'assets_id':id_list
|
||||
};
|
||||
function error(data) {
|
||||
toastr.error(JSON.parse(data).error)
|
||||
function fail(data) {
|
||||
toastr.error(JSON.parse(data))
|
||||
}
|
||||
function success(data) {
|
||||
location.href = data.url;
|
||||
var url = "{% url 'assets:asset-bulk-update' %}";
|
||||
location.href= setUrlParam(url, 'spm', data.spm);
|
||||
}
|
||||
APIUpdateAttr({
|
||||
'url': "{% url 'api-assets:asset-bulk-update-select' %}",
|
||||
'method': 'POST',
|
||||
'body': JSON.stringify(data),
|
||||
'flash_message': false,
|
||||
'success': success,
|
||||
'error': error,
|
||||
url: "{% url 'api-common:resources-cache' %}",
|
||||
method:'POST',
|
||||
body:JSON.stringify(data),
|
||||
flash_message:false,
|
||||
success:success,
|
||||
error:fail
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function doRemove() {
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (!current_node_id) {
|
||||
return
|
||||
}
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (!current_node_id) {
|
||||
return
|
||||
}
|
||||
|
||||
var data = {
|
||||
'assets': id_list
|
||||
};
|
||||
var data = {
|
||||
'assets': id_list
|
||||
};
|
||||
|
||||
var success = function () {
|
||||
asset_table.ajax.reload()
|
||||
};
|
||||
var success = function () {
|
||||
asset_table.ajax.reload()
|
||||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
})
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
})
|
||||
}
|
||||
|
||||
switch(action) {
|
||||
case 'deactive':
|
||||
doDeactive();
|
||||
|
|
|
@ -133,6 +133,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
@ -160,12 +161,13 @@ function initAssetsTable() {
|
|||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var push_btn = '';
|
||||
{% 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 %}
|
||||
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 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 %}',
|
||||
|
@ -360,5 +362,11 @@ $(document).ready(function () {
|
|||
$('#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>
|
||||
{% endblock %}
|
||||
|
|
|
@ -14,6 +14,28 @@
|
|||
{% 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 %}
|
||||
|
@ -41,9 +63,12 @@
|
|||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_system_user_import_modal.html' %}
|
||||
{% include 'assets/_system_user_update_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var system_user_table = 0;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#system_user_list_table'),
|
||||
|
@ -101,7 +126,8 @@ function initTable() {
|
|||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
system_user_table = jumpserver.initServerSideDataTable(options);
|
||||
return system_user_table
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
|
@ -173,6 +199,71 @@ $(document).ready(function(){
|
|||
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>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@ from django.contrib.messages.views import SuccessMessageMixin
|
|||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
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 orgs.utils import current_org
|
||||
from .. import forms
|
||||
|
@ -122,7 +124,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
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'):
|
||||
self.form = kwargs['form']
|
||||
elif assets_id:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
|
||||
import uuid
|
||||
import time
|
||||
|
||||
from django.core.cache import cache
|
||||
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.response import Response
|
||||
from rest_framework.generics import CreateAPIView
|
||||
from rest_framework.views import APIView
|
||||
|
||||
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 users.serializers import UserSerializer
|
||||
from users.models import User
|
||||
|
@ -23,12 +25,13 @@ from users.utils import (
|
|||
check_user_valid, check_otp_code, increase_login_failed_count,
|
||||
is_block_login, clean_failed_count
|
||||
)
|
||||
|
||||
from ..serializers import OtpVerifySerializer
|
||||
from ..signals import post_auth_success, post_auth_failed
|
||||
|
||||
logger = get_logger(__name__)
|
||||
__all__ = [
|
||||
'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi',
|
||||
'UserOtpVerifyApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -179,3 +182,20 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView):
|
|||
sender=self.__class__, username=username,
|
||||
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.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion
|
||||
|
||||
from users.utils import construct_user_email
|
||||
|
||||
logger = _LDAPConfig.get_logger()
|
||||
|
||||
|
||||
|
@ -86,13 +88,18 @@ class LDAPUser(_LDAPUser):
|
|||
return user_dn
|
||||
|
||||
def _populate_user_from_attributes(self):
|
||||
super()._populate_user_from_attributes()
|
||||
if not hasattr(self._user, 'email') or '@' not in self._user.email:
|
||||
if '@' not in self._user.username:
|
||||
email = '{}@{}'.format(self._user.username, settings.EMAIL_SUFFIX)
|
||||
for field, attr in self.settings.USER_ATTR_MAP.items():
|
||||
try:
|
||||
value = self.attrs[attr][0]
|
||||
except LookupError:
|
||||
logger.warning("{} does not have a value for the attribute {}".format(self.dn, attr))
|
||||
else:
|
||||
email = self._user.username
|
||||
setattr(self._user, 'email', email)
|
||||
|
||||
|
||||
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)
|
||||
|
|
|
@ -23,15 +23,12 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
|
|||
def process_request(self, request):
|
||||
# Don't need openid auth if AUTH_OPENID is False
|
||||
if not settings.AUTH_OPENID:
|
||||
logger.debug("Not settings.AUTH_OPENID")
|
||||
return
|
||||
# Don't need check single logout if user not authenticated
|
||||
if not request.user.is_authenticated:
|
||||
logger.debug("User is not authenticated")
|
||||
return
|
||||
elif not request.session[BACKEND_SESSION_KEY].endswith(
|
||||
BACKEND_OPENID_AUTH_CODE):
|
||||
logger.debug("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE")
|
||||
return
|
||||
|
||||
# Check openid user single logout or not with access_token
|
||||
|
|
|
@ -14,3 +14,7 @@ class AccessKeySerializer(serializers.ModelSerializer):
|
|||
model = AccessKey
|
||||
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/',
|
||||
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||
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 uuid
|
||||
|
||||
from rest_framework.views import Response
|
||||
from rest_framework import generics, serializers
|
||||
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):
|
||||
output = serializers.CharField()
|
||||
|
@ -68,3 +76,14 @@ class LogTailApi(generics.RetrieveAPIView):
|
|||
|
||||
data, end, new_mark = self.read_from_file()
|
||||
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")
|
||||
FILE_END_GUARD = ">>> Content End <<<"
|
||||
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',
|
||||
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
|
||||
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
|
||||
'EncryptTextField', 'EncryptMixin',
|
||||
'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
|
||||
]
|
||||
signer = get_signer()
|
||||
|
||||
|
@ -129,4 +129,7 @@ class EncryptCharField(EncryptMixin, models.CharField):
|
|||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
from django.db import models
|
||||
from django.http import JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.utils import html
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import SkipField
|
||||
|
||||
from .const import KEY_CACHE_RESOURCES_ID
|
||||
|
||||
|
||||
class NoDeleteQuerySet(models.query.QuerySet):
|
||||
|
||||
|
@ -65,6 +68,27 @@ class IDInFilterMixin(object):
|
|||
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):
|
||||
"""
|
||||
Become rest_framework_bulk not support uuid as a primary key
|
||||
|
@ -131,7 +155,11 @@ class BulkListSerializerMixin(object):
|
|||
for item in data:
|
||||
try:
|
||||
# prepare child serializer to only handle one instance
|
||||
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
|
||||
if 'id' in item.keys():
|
||||
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
|
||||
# raw
|
||||
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 -*-
|
||||
#
|
||||
import time
|
||||
|
||||
from rest_framework import permissions
|
||||
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):
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||
|
||||
if x_forwarded_for and x_forwarded_for[0]:
|
||||
login_ip = x_forwarded_for[0]
|
||||
else:
|
||||
|
|
|
@ -194,7 +194,7 @@ class Config(dict):
|
|||
filename = os.path.join(self.root_path, filename)
|
||||
try:
|
||||
with open(filename, 'rt', encoding='utf8') as f:
|
||||
obj = yaml.load(f)
|
||||
obj = yaml.safe_load(f)
|
||||
except IOError as e:
|
||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||
return False
|
||||
|
@ -273,6 +273,19 @@ class Config(dict):
|
|||
if default_value is None:
|
||||
return v
|
||||
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:
|
||||
v = tp(v)
|
||||
except Exception:
|
||||
|
@ -289,14 +302,10 @@ class Config(dict):
|
|||
except KeyError:
|
||||
value = None
|
||||
if value is not None:
|
||||
return self.convert_type(item, value)
|
||||
return value
|
||||
# 其次从环境变量来
|
||||
value = os.environ.get(item, 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.defaults.get(item)
|
||||
|
||||
|
@ -343,6 +352,7 @@ defaults = {
|
|||
'TERMINAL_SESSION_KEEP_DURATION': 9999,
|
||||
'TERMINAL_HOST_KEY': '',
|
||||
'TERMINAL_TELNET_REGEX': '',
|
||||
'TERMINAL_COMMAND_STORAGE': {},
|
||||
'SECURITY_MFA_AUTH': False,
|
||||
'SECURITY_LOGIN_LIMIT_COUNT': 7,
|
||||
'SECURITY_LOGIN_LIMIT_TIME': 30,
|
||||
|
@ -361,6 +371,7 @@ defaults = {
|
|||
'HTTP_LISTEN_PORT': 8080,
|
||||
'LOGIN_LOG_KEEP_DAYS': 90,
|
||||
'ASSETS_PERM_CACHE_TIME': 3600,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
VERSION = '1.4.10'
|
||||
VERSION = '1.5.0'
|
||||
|
|
|
@ -67,6 +67,7 @@ INSTALLED_APPS = [
|
|||
'terminal.apps.TerminalConfig',
|
||||
'audits.apps.AuditsConfig',
|
||||
'authentication.apps.AuthenticationConfig', # authentication
|
||||
'applications.apps.ApplicationsConfig',
|
||||
'rest_framework',
|
||||
'rest_framework_swagger',
|
||||
'drf_yasg',
|
||||
|
@ -172,7 +173,7 @@ DATABASES = {
|
|||
'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':
|
||||
DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"
|
||||
if os.path.isfile(DB_CA_PATH):
|
||||
|
@ -356,12 +357,30 @@ EMAIL_USE_SSL = False
|
|||
EMAIL_USE_TLS = False
|
||||
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 = {
|
||||
# Use Django's standard `django.contrib.auth` permissions,
|
||||
# or allow read-only access for unauthenticated users.
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'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': (
|
||||
# 'rest_framework.authentication.BasicAuthentication',
|
||||
'authentication.backends.api.AccessKeyAuthentication',
|
||||
|
@ -374,6 +393,7 @@ REST_FRAMEWORK = {
|
|||
'rest_framework.filters.SearchFilter',
|
||||
'rest_framework.filters.OrderingFilter',
|
||||
),
|
||||
'DEFAULT_METADATA_CLASS': 'common.drfmetadata.SimpleMetadataWithFilters',
|
||||
'ORDERING_PARAM': "order",
|
||||
'SEARCH_PARAM': "search",
|
||||
'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_START_TLS = False
|
||||
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_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||
|
@ -507,12 +533,7 @@ DEFAULT_TERMINAL_COMMAND_STORAGE = {
|
|||
},
|
||||
}
|
||||
|
||||
TERMINAL_COMMAND_STORAGE = {
|
||||
# 'ali-es': {
|
||||
# 'TYPE': 'elasticsearch',
|
||||
# 'HOSTS': ['http://elastic:changeme@localhost:9200'],
|
||||
# },
|
||||
}
|
||||
TERMINAL_COMMAND_STORAGE = CONFIG.TERMINAL_COMMAND_STORAGE
|
||||
|
||||
DEFAULT_TERMINAL_REPLAY_STORAGE = {
|
||||
"default": {
|
||||
|
|
|
@ -20,6 +20,8 @@ api_v1 = [
|
|||
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
||||
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
|
||||
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
|
||||
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
||||
path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')),
|
||||
]
|
||||
|
||||
api_v2 = [
|
||||
|
@ -37,6 +39,7 @@ app_view_patterns = [
|
|||
path('audits/', include('audits.urls.view_urls', namespace='audits')),
|
||||
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
|
||||
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
||||
path('applications/', include('applications.urls.views_urls', namespace='applications')),
|
||||
]
|
||||
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -17,58 +17,58 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: static/js/jumpserver.js:168
|
||||
#: static/js/jumpserver.js:249
|
||||
msgid "Update is successful!"
|
||||
msgstr "更新成功"
|
||||
|
||||
#: static/js/jumpserver.js:170
|
||||
#: static/js/jumpserver.js:251
|
||||
msgid "An unknown error occurred while updating.."
|
||||
msgstr "更新时发生未知错误"
|
||||
|
||||
#: static/js/jumpserver.js:236 static/js/jumpserver.js:273
|
||||
#: static/js/jumpserver.js:276
|
||||
#: static/js/jumpserver.js:315 static/js/jumpserver.js:352
|
||||
#: static/js/jumpserver.js:355
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
#: static/js/jumpserver.js:236
|
||||
#: static/js/jumpserver.js:315
|
||||
msgid "Being used by the asset, please unbind the asset first."
|
||||
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"
|
||||
msgstr "删除成功"
|
||||
|
||||
#: static/js/jumpserver.js:248
|
||||
#: static/js/jumpserver.js:327
|
||||
msgid "Are you sure about deleting it?"
|
||||
msgstr "你确定删除吗 ?"
|
||||
|
||||
#: static/js/jumpserver.js:252 static/js/jumpserver.js:293
|
||||
#: static/js/jumpserver.js:331 static/js/jumpserver.js:372
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: static/js/jumpserver.js:254 static/js/jumpserver.js:295
|
||||
#: static/js/jumpserver.js:333 static/js/jumpserver.js:374
|
||||
msgid "Confirm"
|
||||
msgstr "确认"
|
||||
|
||||
#: static/js/jumpserver.js:273
|
||||
#: static/js/jumpserver.js:352
|
||||
msgid ""
|
||||
"The organization contains undeleted information. Please try again after "
|
||||
"deleting"
|
||||
msgstr "组织中包含未删除信息,请删除后重试"
|
||||
|
||||
#: static/js/jumpserver.js:276
|
||||
#: static/js/jumpserver.js:355
|
||||
msgid ""
|
||||
"Do not perform this operation under this organization. Try again after "
|
||||
"switching to another organization"
|
||||
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
|
||||
|
||||
#: static/js/jumpserver.js:289
|
||||
#: static/js/jumpserver.js:368
|
||||
msgid ""
|
||||
"Please ensure that the following information in the organization has been "
|
||||
"deleted"
|
||||
msgstr "请确保组织内的以下信息已删除"
|
||||
|
||||
#: static/js/jumpserver.js:290
|
||||
#: static/js/jumpserver.js:369
|
||||
msgid ""
|
||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||
"Labels、Asset permission"
|
||||
|
@ -76,52 +76,76 @@ msgstr ""
|
|||
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
|
||||
"规则"
|
||||
|
||||
#: static/js/jumpserver.js:329
|
||||
#: static/js/jumpserver.js:408
|
||||
msgid "Loading ..."
|
||||
msgstr "加载中 ..."
|
||||
|
||||
#: static/js/jumpserver.js:330
|
||||
#: static/js/jumpserver.js:409
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
#: static/js/jumpserver.js:333
|
||||
#: static/js/jumpserver.js:412
|
||||
#, javascript-format
|
||||
msgid "Selected item %d"
|
||||
msgstr "选中 %d 项"
|
||||
|
||||
#: static/js/jumpserver.js:337
|
||||
#: static/js/jumpserver.js:416
|
||||
msgid "Per page _MENU_"
|
||||
msgstr "每页 _MENU_"
|
||||
|
||||
#: static/js/jumpserver.js:338
|
||||
#: static/js/jumpserver.js:417
|
||||
msgid ""
|
||||
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
|
||||
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
|
||||
|
||||
#: static/js/jumpserver.js:341
|
||||
#: static/js/jumpserver.js:420
|
||||
msgid "No match"
|
||||
msgstr "没有匹配项"
|
||||
|
||||
#: static/js/jumpserver.js:342
|
||||
#: static/js/jumpserver.js:421
|
||||
msgid "No record"
|
||||
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"
|
||||
msgstr "密码最小长度 {N} 位"
|
||||
|
||||
#: static/js/jumpserver.js:702
|
||||
#: static/js/jumpserver.js:801
|
||||
msgid "Must contain capital letters"
|
||||
msgstr "必须包含大写字母"
|
||||
|
||||
#: static/js/jumpserver.js:703
|
||||
#: static/js/jumpserver.js:802
|
||||
msgid "Must contain lowercase letters"
|
||||
msgstr "必须包含小写字母"
|
||||
|
||||
#: static/js/jumpserver.js:704
|
||||
#: static/js/jumpserver.js:803
|
||||
msgid "Must contain numeric characters"
|
||||
msgstr "必须包含数字字符"
|
||||
|
||||
#: static/js/jumpserver.js:705
|
||||
#: static/js/jumpserver.js:804
|
||||
msgid "Must contain special characters"
|
||||
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):
|
||||
pass
|
||||
|
||||
def display_failed_stderr(self):
|
||||
pass
|
||||
|
||||
|
||||
class CommandResultCallback(AdHocResultCallback):
|
||||
"""
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
import os
|
||||
import shutil
|
||||
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.vars.manager import VariableManager
|
||||
from ansible.parsing.dataloader import DataLoader
|
||||
|
@ -33,29 +36,18 @@ Options = namedtuple('Options', [
|
|||
|
||||
|
||||
def get_default_options():
|
||||
options = Options(
|
||||
listtags=False,
|
||||
listtasks=False,
|
||||
listhosts=False,
|
||||
options = dict(
|
||||
syntax=False,
|
||||
timeout=30,
|
||||
connection='ssh',
|
||||
module_path='',
|
||||
forks=10,
|
||||
remote_user='root',
|
||||
private_key_file=None,
|
||||
ssh_common_args="",
|
||||
ssh_extra_args="",
|
||||
sftp_extra_args="",
|
||||
scp_extra_args="",
|
||||
become=None,
|
||||
become_method=None,
|
||||
become_user=None,
|
||||
verbosity=None,
|
||||
extra_vars=[],
|
||||
verbosity=1,
|
||||
check=False,
|
||||
playbook_path='/etc/ansible/',
|
||||
passwords=None,
|
||||
diff=False,
|
||||
gathering='implicit',
|
||||
remote_tmp='/tmp/.ansible'
|
||||
|
@ -108,9 +100,9 @@ class PlayBookRunner:
|
|||
inventory=self.inventory,
|
||||
variable_manager=self.variable_manager,
|
||||
loader=self.loader,
|
||||
options=self.options,
|
||||
passwords=self.passwords
|
||||
passwords={"conn_pass": self.passwords}
|
||||
)
|
||||
context.CLIARGS = ImmutableDict(self.options)
|
||||
|
||||
if executor._tqm:
|
||||
executor._tqm._stdout_callback = self.results_callback
|
||||
|
@ -185,11 +177,10 @@ class AdHocRunner:
|
|||
return cleaned_tasks
|
||||
|
||||
def update_options(self, options):
|
||||
_options = {k: v for k, v in self.default_options.items()}
|
||||
if options and isinstance(options, dict):
|
||||
options = self.__class__.default_options._replace(**options)
|
||||
else:
|
||||
options = self.__class__.default_options
|
||||
return options
|
||||
_options.update(options)
|
||||
return _options
|
||||
|
||||
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
|
||||
"""
|
||||
|
@ -202,6 +193,7 @@ class AdHocRunner:
|
|||
self.check_pattern(pattern)
|
||||
self.results_callback = self.get_result_callback()
|
||||
cleaned_tasks = self.clean_tasks(tasks)
|
||||
context.CLIARGS = ImmutableDict(self.options)
|
||||
|
||||
play_source = dict(
|
||||
name=play_name,
|
||||
|
@ -220,9 +212,8 @@ class AdHocRunner:
|
|||
inventory=self.inventory,
|
||||
variable_manager=self.variable_manager,
|
||||
loader=self.loader,
|
||||
options=self.options,
|
||||
stdout_callback=self.results_callback,
|
||||
passwords=self.options.passwords,
|
||||
passwords={"conn_pass": self.options.get("password", "")}
|
||||
)
|
||||
try:
|
||||
tqm.run(play)
|
||||
|
@ -230,8 +221,9 @@ class AdHocRunner:
|
|||
except Exception as e:
|
||||
raise AnsibleError(e)
|
||||
finally:
|
||||
tqm.cleanup()
|
||||
self.loader.cleanup_all_tmp_files()
|
||||
if tqm is not None:
|
||||
tqm.cleanup()
|
||||
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
|
||||
|
||||
|
||||
class CommandRunner(AdHocRunner):
|
||||
|
|
|
@ -15,7 +15,7 @@ class TestAdHocRunner(unittest.TestCase):
|
|||
host_data = [
|
||||
{
|
||||
"hostname": "testserver",
|
||||
"ip": "192.168.244.168",
|
||||
"ip": "192.168.244.185",
|
||||
"port": 22,
|
||||
"username": "root",
|
||||
"password": "redhat",
|
||||
|
|
|
@ -35,7 +35,6 @@ class JMSBaseInventory(BaseInventory):
|
|||
info["vars"].update({
|
||||
label.name: label.value
|
||||
})
|
||||
info["groups"].append("{}:{}".format(label.name, label.value))
|
||||
if asset.domain:
|
||||
info["vars"].update({
|
||||
"domain": asset.domain.name,
|
||||
|
|
|
@ -8,9 +8,12 @@ from django.shortcuts import redirect, get_object_or_404
|
|||
from django.forms import ModelForm
|
||||
from django.http.response import HttpResponseForbidden
|
||||
from django.core.exceptions import ValidationError
|
||||
from rest_framework import serializers
|
||||
|
||||
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
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -18,7 +21,8 @@ tl = Local()
|
|||
|
||||
__all__ = [
|
||||
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
|
||||
'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'OrgMembershipModelViewSetMixin'
|
||||
'RootOrgViewMixin', 'OrgMembershipSerializerMixin',
|
||||
'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin',
|
||||
]
|
||||
|
||||
|
||||
|
@ -202,3 +206,11 @@ class OrgMembershipModelViewSetMixin:
|
|||
def get_queryset(self):
|
||||
queryset = self.membership_class.objects.filter(organization=self.org)
|
||||
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')
|
||||
|
||||
|
||||
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'))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .permission import *
|
||||
from .asset_permission import *
|
||||
from .user_permission import *
|
||||
from .user_group_permission import *
|
||||
from .remote_app_permission import *
|
||||
|
|
|
@ -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 orgs.utils import set_to_root_org
|
||||
from ..utils import (
|
||||
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node
|
||||
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
|
||||
RemoteAppPermissionUtil,
|
||||
)
|
||||
from ..hands import (
|
||||
AssetGrantedSerializer, UserGroup, Node, NodeSerializer
|
||||
AssetGrantedSerializer, UserGroup, Node, NodeSerializer,
|
||||
RemoteAppSerializer,
|
||||
)
|
||||
from .. import serializers
|
||||
|
||||
|
@ -22,6 +24,7 @@ __all__ = [
|
|||
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
|
||||
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
|
||||
'UserGroupGrantedNodesWithAssetsAsTreeApi',
|
||||
'UserGroupGrantedRemoteAppsApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -138,3 +141,20 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
|
|||
for asset, system_users in assets.items():
|
||||
asset.system_users_granted = system_users
|
||||
return assets
|
||||
|
||||
|
||||
# RemoteApp permission
|
||||
|
||||
class UserGroupGrantedRemoteAppsApi(ListAPIView):
|
||||
permission_classes = (IsOrgAdmin, )
|
||||
serializer_class = RemoteAppSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
user_group_id = self.kwargs.get('pk')
|
||||
if not user_group_id:
|
||||
return queryset
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
util = RemoteAppPermissionUtil(user_group)
|
||||
queryset = util.get_remote_apps()
|
||||
return queryset
|
||||
|
|
|
@ -17,14 +17,15 @@ from common.utils import get_logger
|
|||
from orgs.utils import set_to_root_org
|
||||
from ..utils import (
|
||||
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
|
||||
check_system_user_action
|
||||
check_system_user_action, RemoteAppPermissionUtil,
|
||||
construct_remote_apps_tree_root, parse_remote_app_to_tree_node,
|
||||
)
|
||||
from ..hands import (
|
||||
AssetGrantedSerializer, User, Asset, Node,
|
||||
SystemUser, NodeSerializer
|
||||
User, Asset, Node, SystemUser, RemoteApp, AssetGrantedSerializer,
|
||||
NodeSerializer, RemoteAppSerializer,
|
||||
)
|
||||
from .. import serializers
|
||||
from ..mixins import AssetsFilterMixin
|
||||
from ..mixins import AssetsFilterMixin, RemoteAppFilterMixin
|
||||
from ..models import Action
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -34,6 +35,8 @@ __all__ = [
|
|||
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
|
||||
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
|
||||
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
|
||||
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
|
||||
'UserGrantedRemoteAppsAsTreeApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -447,3 +450,79 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
|
|||
|
||||
actions = [action.name for action in getattr(_su, 'actions', [])]
|
||||
return Response({'actions': actions}, status=200)
|
||||
|
||||
|
||||
# RemoteApp permission
|
||||
|
||||
class UserGrantedRemoteAppsApi(RemoteAppFilterMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = RemoteAppSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_object(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
if user_id:
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
else:
|
||||
user = self.request.user
|
||||
return user
|
||||
|
||||
def get_queryset(self):
|
||||
util = RemoteAppPermissionUtil(self.get_object())
|
||||
queryset = util.get_remote_apps()
|
||||
queryset = list(queryset)
|
||||
return queryset
|
||||
|
||||
def get_permissions(self):
|
||||
if self.kwargs.get('pk') is None:
|
||||
self.permission_classes = (IsValidUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class UserGrantedRemoteAppsAsTreeApi(ListAPIView):
|
||||
serializer_class = TreeNodeSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_object(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
if not user_id:
|
||||
user = self.request.user
|
||||
else:
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
return user
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
tree_root = construct_remote_apps_tree_root()
|
||||
queryset.append(tree_root)
|
||||
|
||||
util = RemoteAppPermissionUtil(self.get_object())
|
||||
remote_apps = util.get_remote_apps()
|
||||
for remote_app in remote_apps:
|
||||
node = parse_remote_app_to_tree_node(tree_root, remote_app)
|
||||
queryset.append(node)
|
||||
|
||||
queryset = sorted(queryset)
|
||||
return queryset
|
||||
|
||||
def get_permissions(self):
|
||||
if self.kwargs.get('pk') is None:
|
||||
self.permission_classes = (IsValidUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class ValidateUserRemoteAppPermissionApi(APIView):
|
||||
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.utils import current_org
|
||||
from .models import AssetPermission
|
||||
from perms.models import AssetPermission
|
||||
from assets.models import Asset
|
||||
|
||||
__all__ = [
|
||||
'AssetPermissionForm',
|
||||
]
|
||||
|
||||
|
||||
class AssetPermissionForm(OrgModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
|
@ -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 users.models import User, UserGroup
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
from assets.serializers import AssetGrantedSerializer, NodeSerializer
|
||||
from assets.models import Asset, SystemUser, Node, RemoteApp
|
||||
from assets.serializers import (
|
||||
AssetGrantedSerializer, NodeSerializer
|
||||
)
|
||||
from applications.serializers import RemoteAppSerializer
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
对资产进行过滤(查询,排序)
|
||||
|
@ -34,3 +39,38 @@ class AssetsFilterMixin(object):
|
|||
|
||||
queryset = sort_assets(queryset, order_by=order_by, reverse=reverse)
|
||||
return queryset
|
||||
|
||||
|
||||
class RemoteAppFilterMixin(object):
|
||||
"""
|
||||
对RemoteApp进行过滤(查询,排序)
|
||||
"""
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = self.search_remote_apps(queryset)
|
||||
queryset = self.sort_remote_apps(queryset)
|
||||
return queryset
|
||||
|
||||
def search_remote_apps(self, queryset):
|
||||
value = self.request.query_params.get('search')
|
||||
if not value:
|
||||
return queryset
|
||||
queryset = [
|
||||
remote_app for remote_app in queryset if value in remote_app.name
|
||||
]
|
||||
return queryset
|
||||
|
||||
def sort_remote_apps(self, queryset):
|
||||
order_by = self.request.query_params.get('order')
|
||||
if not order_by:
|
||||
order_by = 'name'
|
||||
if order_by.startswith('-'):
|
||||
order_by = order_by.lstrip('-')
|
||||
reverse = True
|
||||
else:
|
||||
reverse = False
|
||||
|
||||
queryset = sorted(
|
||||
queryset, key=lambda x: getattr(x, order_by), reverse=reverse
|
||||
)
|
||||
return queryset
|
||||
|
|
|
@ -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.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
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):
|
||||
|
@ -28,69 +33,16 @@ class Action(models.Model):
|
|||
return cls.objects.get(name=PERMS_ACTION_NAME_ALL)
|
||||
|
||||
|
||||
class AssetPermissionQuerySet(models.QuerySet):
|
||||
def active(self):
|
||||
return self.filter(is_active=True)
|
||||
|
||||
def valid(self):
|
||||
return self.active().filter(date_start__lt=timezone.now())\
|
||||
.filter(date_expired__gt=timezone.now())
|
||||
|
||||
|
||||
class 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"))
|
||||
class AssetPermission(BasePermission):
|
||||
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"))
|
||||
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'))
|
||||
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:
|
||||
unique_together = [('org_id', 'name')]
|
||||
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):
|
||||
assets = set(self.assets.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