mirror of https://github.com/jumpserver/jumpserver
[Feature] 打算拆分下载和上传为独立模块,时间有限暂时放弃
parent
160b01ec12
commit
08e1788426
|
@ -9,6 +9,7 @@ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen,
|
|||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
|
||||
|
@ -240,7 +241,7 @@ class SystemUserForm(forms.ModelForm):
|
|||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||
'comment', 'shell', 'cluster'
|
||||
'comment', 'shell', 'cluster', 'priority',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
|
@ -254,6 +255,7 @@ class SystemUserForm(forms.ModelForm):
|
|||
'username': '* required',
|
||||
'cluster': 'If auto push checked, system user will be create at cluster assets',
|
||||
'auto_push': 'Auto push system user to asset',
|
||||
'priority': 'High level will be using login asset as default, if user was granted more than 2 system user',
|
||||
}
|
||||
|
||||
|
||||
|
@ -261,7 +263,7 @@ class SystemUserUpdateForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol',
|
||||
'name', 'username', 'protocol', 'priority',
|
||||
'sudo', 'comment', 'shell', 'cluster'
|
||||
]
|
||||
widgets = {
|
||||
|
@ -275,6 +277,7 @@ class SystemUserUpdateForm(forms.ModelForm):
|
|||
'name': '* required',
|
||||
'username': '* required',
|
||||
'cluster': 'If auto push checked, then push system user to that cluster assets',
|
||||
'priority': 'High level will be using login asset as default, if user was granted more than 2 system user',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import logging
|
|||
import uuid
|
||||
from hashlib import md5
|
||||
|
||||
import sshpubkeys
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
@ -27,7 +28,8 @@ class AssetUser(models.Model):
|
|||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
|
||||
|
||||
@property
|
||||
|
@ -45,16 +47,21 @@ class AssetUser(models.Model):
|
|||
@property
|
||||
def private_key(self):
|
||||
if self._private_key:
|
||||
key_str = signer.unsign(self._private_key)
|
||||
return ssh_key_string_to_obj(key_str, password=self.password)
|
||||
else:
|
||||
return None
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
raise AttributeError("Using set_auth do that")
|
||||
# self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self._private_key:
|
||||
key_str = signer.unsign(self._private_key)
|
||||
return ssh_key_string_to_obj(key_str, password=self.password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key:
|
||||
|
@ -74,6 +81,15 @@ class AssetUser(models.Model):
|
|||
def public_key(self):
|
||||
return signer.unsign(self._public_key)
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
try:
|
||||
return sshpubkeys.SSHKey(self.public_key)
|
||||
except TabError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
|
@ -170,6 +186,7 @@ class SystemUser(AssetUser):
|
|||
('K', 'Public key'),
|
||||
)
|
||||
cluster = models.ManyToManyField('assets.Cluster', verbose_name=_("Cluster"))
|
||||
priority = models.IntegerField(default=10, verbose_name=_("Priority")) # Todo: If user granted more priority user, default will be login as the hign
|
||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
|
||||
|
@ -205,6 +222,7 @@ class SystemUser(AssetUser):
|
|||
'name': self.name,
|
||||
'username': self.username,
|
||||
'protocol': self.protocol,
|
||||
'priority': self.priority,
|
||||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username', 'protocol', 'comment')
|
||||
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
|
||||
|
||||
|
||||
class SystemUserUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.priority layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.cluster layout="horizontal" %}
|
||||
|
||||
|
@ -49,7 +50,6 @@
|
|||
{{ form.auto_generate_key}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="auth-fields">
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
|
|
|
@ -64,7 +64,9 @@
|
|||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
$("#tags").select2({
|
||||
tags: true,
|
||||
maximumSelectionLength: 8 //最多能够选择的个数
|
||||
|
|
|
@ -123,7 +123,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
|||
|
||||
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = AdminUser
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
|
||||
|
||||
|
|
|
@ -9,12 +9,13 @@ import chardet
|
|||
from io import StringIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured, FieldDoesNotExist
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect, Http404
|
||||
from django.views.decorators.csrf import csrf_protect, csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.core.cache import cache
|
||||
|
@ -22,8 +23,10 @@ from django.utils import timezone
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import get_object_or_404, redirect, reverse
|
||||
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none
|
||||
from common.imexp import ModelExportView
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
@ -169,7 +172,7 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
|
||||
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Asset
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
|
||||
|
@ -193,46 +196,11 @@ class AssetDetailView(DetailView):
|
|||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(View):
|
||||
def get(self, request):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [1]
|
||||
assets_id = cache.get(spm, assets_id_default)
|
||||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created'
|
||||
]
|
||||
]
|
||||
filename = 'assets-{}.csv'.format(
|
||||
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
response.write(codecs.BOM_UTF8)
|
||||
assets = Asset.objects.filter(id__in=assets_id)
|
||||
writer = csv.writer(response, dialect='excel',
|
||||
quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
header = [field.verbose_name for field in fields]
|
||||
header.append(_('Asset groups'))
|
||||
writer.writerow(header)
|
||||
|
||||
for asset in assets:
|
||||
groups = ','.join([group.name for group in asset.groups.all()])
|
||||
data = [getattr(asset, field.name) for field in fields]
|
||||
data.append(groups)
|
||||
writer.writerow(data)
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
assets_id = json.loads(request.body).get('assets_id', [])
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
spm = uuid.uuid4().hex
|
||||
cache.set(spm, assets_id, 300)
|
||||
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
|
||||
return JsonResponse({'redirect': url})
|
||||
class AssetExportView(ModelExportView):
|
||||
filename_prefix = 'jumpserver'
|
||||
redirect_url = reverse_lazy('assets:asset-export')
|
||||
model = Asset
|
||||
fields = ('hostname', 'ip')
|
||||
|
||||
|
||||
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
|
|
|
@ -97,5 +97,5 @@ class ClusterAssetsView(AdminUserRequiredMixin, DetailView):
|
|||
|
||||
class ClusterDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Cluster
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:cluster-list')
|
||||
|
|
|
@ -101,6 +101,6 @@ class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
|
||||
|
||||
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
template_name = 'delete_confirm.html'
|
||||
model = AssetGroup
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
|
|
|
@ -99,7 +99,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
|||
|
||||
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = SystemUser
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import codecs
|
||||
import csv
|
||||
import uuid
|
||||
import json
|
||||
from io import StringIO
|
||||
import warnings
|
||||
import chardet
|
||||
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.views import View
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ImproperlyConfigured, FieldDoesNotExist
|
||||
from django.utils.encoding import force_text
|
||||
from django.http import Http404, HttpResponseRedirect, HttpResponse, JsonResponse
|
||||
|
||||
|
||||
class ModelExportPostMixin:
|
||||
"""
|
||||
将用户post上来的数据转存到cache, 生成一个uuid, redirect 到GET URL
|
||||
"""
|
||||
redirect_url = None
|
||||
error_message = 'Json object not valid'
|
||||
keyword = 'spm'
|
||||
cache_key = None
|
||||
request = None
|
||||
|
||||
def get_redirect_url(self):
|
||||
if self.redirect_url:
|
||||
# Forcing possible reverse_lazy evaluation
|
||||
url = force_text(self.redirect_url)
|
||||
else:
|
||||
msg = "No URL to redirect to. Provide a redirect_url."
|
||||
raise ImproperlyConfigured(msg)
|
||||
sep = "?" if url.find('?') else '&'
|
||||
url = '{}{}{}={}'.format(url, sep, self.keyword, self.cache_key)
|
||||
return url
|
||||
|
||||
def save_objects_id_to_cache(self, objects_id):
|
||||
self.cache_key = uuid.uuid4().hex
|
||||
cache.set(self.cache_key, objects_id, 300)
|
||||
return self.cache_key
|
||||
|
||||
def get_objects_id_from_request(self):
|
||||
try:
|
||||
objects_id = json.loads(self.request.body)
|
||||
except ValueError:
|
||||
raise Http404(self.error_message)
|
||||
return objects_id
|
||||
|
||||
def get_redirect_response(self):
|
||||
objects_id = self.get_objects_id_from_request()
|
||||
self.save_objects_id_to_cache(objects_id)
|
||||
url = self.get_redirect_url()
|
||||
return HttpResponseRedirect(redirect_to=url)
|
||||
|
||||
# View need implement it
|
||||
# def post(self, request, *args, **kwargs):
|
||||
# self.request = request
|
||||
# return self.get_redirect_response()
|
||||
|
||||
|
||||
class MethodField:
|
||||
def __init__(self, name, verbose_name=None):
|
||||
self.name = name
|
||||
self.verbose_name = verbose_name
|
||||
|
||||
if self.verbose_name is None:
|
||||
self.verbose_name = name
|
||||
|
||||
|
||||
class FieldCheckMeta(type):
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
error = cls.validate_fields(attrs)
|
||||
if not error:
|
||||
return super().__new__(cls, name, bases, attrs)
|
||||
else:
|
||||
raise AttributeError(error)
|
||||
|
||||
@staticmethod
|
||||
def validate_fields(attrs):
|
||||
model = attrs.get('model')
|
||||
fields = attrs.get('fields')
|
||||
if model is None or fields in ('__all__', None):
|
||||
return None
|
||||
|
||||
all_attr = [attr for attr in dir(model) if not attr.startswith('_')]
|
||||
invalid_fields = []
|
||||
|
||||
for field in fields:
|
||||
if field not in all_attr:
|
||||
invalid_fields.append(field)
|
||||
|
||||
if not invalid_fields:
|
||||
return None
|
||||
|
||||
error = 'model {} is not have `{}` attr, check `fields` setting'.format(
|
||||
model._meta.model_name, ', '.join(invalid_fields)
|
||||
)
|
||||
return error
|
||||
|
||||
|
||||
class ModelFieldsMixin(metaclass=FieldCheckMeta):
|
||||
model = None
|
||||
fields = None
|
||||
exclude = None
|
||||
errors = None
|
||||
__cleaned_fields_name = None
|
||||
__is_valid = False
|
||||
__defined_fields_name = None
|
||||
|
||||
def get_define_fields_name(self):
|
||||
"""
|
||||
Calculate fields, fields may be `__all__`, `(field1, field2)` or
|
||||
set `exclude` so do that
|
||||
:return: => list
|
||||
"""
|
||||
if self.__defined_fields_name:
|
||||
return self.__defined_fields_name
|
||||
|
||||
all_fields = [field.name for field in self.model._meta.fields]
|
||||
if self.fields == '__all__':
|
||||
return all_fields
|
||||
elif self.fields:
|
||||
return self.fields
|
||||
elif self.exclude:
|
||||
return list(set(all_fields) - set(self.exclude))
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_field(self, field_name):
|
||||
try:
|
||||
return self.model._meta.get_field(field_name)
|
||||
except FieldDoesNotExist:
|
||||
attr = getattr(self.model, field_name)
|
||||
if hasattr(attr, 'verbose_name'):
|
||||
verbose_name = getattr(attr, 'verbose_name')
|
||||
else:
|
||||
verbose_name = field_name
|
||||
return MethodField(field_name, verbose_name)
|
||||
|
||||
def get_fields(self, cleaned_fields_name):
|
||||
"""
|
||||
Get fields by fields name
|
||||
:param cleaned_fields_name:
|
||||
:return:
|
||||
"""
|
||||
fields = []
|
||||
for name in cleaned_fields_name:
|
||||
fields.append(self.get_field(name))
|
||||
return fields
|
||||
|
||||
def get_define_fields(self):
|
||||
fields_name = self.get_define_fields_name()
|
||||
return self.get_fields(fields_name)
|
||||
|
||||
def valid_field_name(self, field_name):
|
||||
if not hasattr(self.model, field_name):
|
||||
msg = "{} not has `{}` attr".format(self.model._meta.model_name, field_name)
|
||||
raise AttributeError(msg)
|
||||
elif field_name not in self.get_define_fields_name():
|
||||
msg = '{} not allowed by server'.format(field_name)
|
||||
raise AttributeError(msg)
|
||||
|
||||
def is_valid(self, fields, ignore_exception=True):
|
||||
self.__cleaned_fields_name = []
|
||||
self.errors = {}
|
||||
|
||||
for field_name in fields:
|
||||
try:
|
||||
self.valid_field_name(field_name)
|
||||
self.__cleaned_fields_name.append(field_name)
|
||||
except AttributeError as e:
|
||||
if not ignore_exception:
|
||||
self.errors[field_name] = str(e)
|
||||
|
||||
if self.errors:
|
||||
self.__is_valid = False
|
||||
return False
|
||||
else:
|
||||
self.__is_valid = True
|
||||
return True
|
||||
|
||||
@property
|
||||
def field_verbose_name_mapping(self):
|
||||
mapping = {}
|
||||
for field in self.get_define_fields():
|
||||
mapping[field.verbose_name] = field.name
|
||||
return mapping
|
||||
|
||||
@property
|
||||
def cleaned_fields(self):
|
||||
if self.__cleaned_fields_name is None:
|
||||
raise AttributeError("Run `is_valid` first")
|
||||
|
||||
if not self.__is_valid:
|
||||
warnings.warn("Is not valid, result may be not complete")
|
||||
|
||||
return self.get_fields(self.__cleaned_fields_name)
|
||||
|
||||
|
||||
class ModelExportGetMixin(ModelFieldsMixin):
|
||||
filename_prefix = 'jumpserver'
|
||||
response = None
|
||||
writer = None
|
||||
model = None
|
||||
objects_id = None
|
||||
queryset = None
|
||||
keyword = 'spm'
|
||||
|
||||
def get_filename(self):
|
||||
now = timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')
|
||||
filename = '{}-{}-{}.csv'.format(
|
||||
self.filename_prefix, self.model._meta.model_name, now
|
||||
)
|
||||
return filename
|
||||
|
||||
def get_objects_id(self):
|
||||
cache_key = self.request.GET.get(self.keyword)
|
||||
self.objects_id = cache.get(cache_key, [])
|
||||
return self.objects_id
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = None
|
||||
|
||||
if self.queryset:
|
||||
queryset = self.queryset
|
||||
elif self.queryset is None:
|
||||
queryset = self.model._meta.default_manager.all()
|
||||
|
||||
if queryset is None:
|
||||
raise AttributeError("Get queryset failed, set `queryset` or `model`")
|
||||
|
||||
objects_id = self.get_objects_id()
|
||||
queryset_filtered = queryset.filter(id__in=objects_id)
|
||||
return queryset_filtered
|
||||
|
||||
def initial_csv_response(self):
|
||||
filename = self.get_filename()
|
||||
self.response = HttpResponse(content_type='text/csv')
|
||||
self.response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
|
||||
self.response.write(codecs.BOM_UTF8)
|
||||
self.writer = csv.writer(self.response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
||||
header = []
|
||||
for field in self.get_define_fields():
|
||||
if hasattr(field, 'verbose_name'):
|
||||
header.append(getattr(field, 'verbose_name'))
|
||||
else:
|
||||
header.append(getattr(field, 'name'))
|
||||
self.writer.writerow(header)
|
||||
|
||||
def make_csv_response(self):
|
||||
self.initial_csv_response()
|
||||
queryset = self.get_queryset()
|
||||
|
||||
for instance in queryset:
|
||||
data = [getattr(instance, field.name) for field in self.get_define_fields()]
|
||||
self.writer.writerow(data)
|
||||
return self.response
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField()
|
||||
|
||||
|
||||
class ModelImportPostMixin(ModelFieldsMixin):
|
||||
form_context = "file"
|
||||
csv_data = None
|
||||
form_class = FileForm
|
||||
stream = None
|
||||
|
||||
def get_form(self):
|
||||
form = self.form_class(self.request.POST)
|
||||
if form.is_valid():
|
||||
raise Http404("Form is not valid")
|
||||
return form
|
||||
|
||||
def get_stream(self):
|
||||
self.stream = self.get_form().cleaned_data[self.form_context]
|
||||
return self.stream
|
||||
|
||||
def get_csv_data(self, stream=None):
|
||||
if stream is None:
|
||||
stream = self.stream
|
||||
result = chardet.detect(stream.read())
|
||||
stream.seek(0)
|
||||
raw_data = stream.read().decode(result['encoding'])\
|
||||
.strip(codecs.BOM_UTF8.decode())
|
||||
csv_file = StringIO(raw_data)
|
||||
reader = csv.reader(csv_file)
|
||||
csv_data = [row for row in reader]
|
||||
self.csv_data = csv_data
|
||||
return csv_data
|
||||
|
||||
def cleaned_post_fields(self):
|
||||
fields = []
|
||||
header = self.csv_data[0]
|
||||
fields_name = [self.field_verbose_name_mapping.get(v) for v in header]
|
||||
for name in fields_name:
|
||||
if name in self.get_define_fields():
|
||||
fields.append(self.get_field(name))
|
||||
else:
|
||||
fields.append(None)
|
||||
return fields
|
||||
|
||||
def create_or_update(self):
|
||||
stream = self.get_stream()
|
||||
csv_data = self.get_csv_data(stream)
|
||||
cleaned_fields = self.cleaned_post_fields()
|
||||
|
||||
|
||||
|
||||
class ModelImportView(ModelImportPostMixin):
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.create_or_update()
|
||||
|
||||
|
||||
class ModelExportView(ModelExportPostMixin, ModelExportGetMixin, View):
|
||||
model = None
|
||||
filename_prefix = 'jumpserver'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.get_redirect_response()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
response = self.make_csv_response()
|
||||
return response
|
|
@ -1067,13 +1067,13 @@ msgstr "配置"
|
|||
msgid "Location"
|
||||
msgstr "位置"
|
||||
|
||||
#: assets/templates/assets/delete_confirm.html:6
|
||||
#: perms/templates/perms/delete_confirm.html:6
|
||||
#: assets/templates/delete_confirm.html:6
|
||||
#: perms/templates/delete_confirm.html:6
|
||||
#: users/templates/users/user_delete_confirm.html:6
|
||||
msgid "Confirm delete"
|
||||
msgstr "确认删除"
|
||||
|
||||
#: assets/templates/assets/delete_confirm.html:11
|
||||
#: assets/templates/delete_confirm.html:11
|
||||
msgid "Are you sure delete"
|
||||
msgstr "您确定删除吗?"
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
|
|||
Asset, AssetGroup
|
||||
from .models import AssetPermission
|
||||
from .forms import AssetPermissionForm
|
||||
from .utils import associate_system_users_and_assets
|
||||
# from .utils import associate_system_users_and_assets
|
||||
|
||||
|
||||
class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
||||
|
@ -87,15 +87,15 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
|
|||
'successfully.'.format(url=url, name=self.object.name))
|
||||
return success_message
|
||||
|
||||
def form_valid(self, form):
|
||||
assets = form.cleaned_data['assets']
|
||||
asset_groups = form.cleaned_data['asset_groups']
|
||||
system_users = form.cleaned_data['system_users']
|
||||
associate_system_users_and_assets(system_users, assets, asset_groups)
|
||||
response = super(AssetPermissionCreateView, self).form_valid(form)
|
||||
self.object.created_by = self.request.user.name
|
||||
self.object.save()
|
||||
return response
|
||||
# Todo: When create push system user
|
||||
# def form_valid(self, form):
|
||||
# assets = form.cleaned_data['assets']
|
||||
# asset_groups = form.cleaned_data['asset_groups']
|
||||
# system_users = form.cleaned_data['system_users']
|
||||
# response = super(AssetPermissionCreateView, self).form_valid(form)
|
||||
# self.object.created_by = self.request.user.name
|
||||
# self.object.save()
|
||||
# return response
|
||||
|
||||
|
||||
class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
|
@ -150,7 +150,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
|
|||
|
||||
class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = AssetPermission
|
||||
template_name = 'perms/delete_confirm.html'
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('perms:asset-permission-list')
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% trans 'Confirm delete' %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% trans 'Are you sure delete' %} <b>{{ object.name }} </b> ?</p>
|
||||
<input type="submit" value="Confirm" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -2,7 +2,7 @@
|
|||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
|
@ -12,6 +12,10 @@
|
|||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_left_head %}
|
||||
123
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
|
@ -26,7 +30,7 @@
|
|||
<select class="select2 form-control" name="username">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for u in user_list %}
|
||||
<option value="{{ u }}" {% if user == u %} selected {% endif %}>{{ u }}</option>
|
||||
<option value="{{ u }}" {% if u == username %} selected {% endif %}>{{ u }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load terminal_tags %}
|
||||
{% block content_left_head %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
#search_btn {
|
||||
|
@ -11,6 +11,9 @@
|
|||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_left_head %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
|
@ -26,7 +29,7 @@
|
|||
<select class="select2 form-control" name="user">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for u in user_list %}
|
||||
<option value="{{ u }}" {% if u == user %} selected {% endif %}>{{ u }}</option>
|
||||
<option value="{{ u }}" {% if u == username %} selected {% endif %}>{{ u }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
@ -106,10 +109,17 @@
|
|||
function success() {
|
||||
window.setTimeout(function () {
|
||||
window.location.reload()
|
||||
}, 300)
|
||||
}, 1000)
|
||||
}
|
||||
var success_message = '{% trans "Terminate task send, waiting ..." %}';
|
||||
var the_url = "{% url 'api-terminal:tasks-list' %}";
|
||||
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
success: success,
|
||||
success_message: success_message
|
||||
});
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
|
@ -118,7 +128,9 @@
|
|||
"bInfo" : false,
|
||||
"order": []
|
||||
});
|
||||
$('.select2').select2();
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true
|
||||
});
|
||||
$('#date .input-daterange').datepicker({
|
||||
dateFormat: 'mm/dd/yy',
|
||||
keyboardNavigation: false,
|
||||
|
@ -135,12 +147,6 @@
|
|||
terminal: terminal_id
|
||||
};
|
||||
terminateSession(data)
|
||||
}).on('click', '#btn_bulk_update', function () {
|
||||
var data = [];
|
||||
$('.cbx-term:checked').each(function () {
|
||||
data.push({proxy_log_id: $(this).attr('value')})
|
||||
});
|
||||
terminateSession(data)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
{% load i18n static %}
|
||||
{% block custom_head_css_js %}
|
||||
{{ block.super }}
|
||||
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
|
@ -15,9 +14,10 @@
|
|||
#modal .modal-body { max-height: 200px; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
{#<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>#}
|
||||
<table class="table table-striped table-bordered table-hover " id="terminal_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -40,12 +40,11 @@
|
|||
</tbody>
|
||||
</table>
|
||||
{% include 'terminal/terminal_modal_accept.html' %}
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#terminal_list_table'),
|
||||
buttons: [],
|
||||
|
@ -100,19 +99,20 @@ $(document).ready(function(){
|
|||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
|
||||
$('#btn_terminal_accept').click(function () {
|
||||
var $form = $('#form_terminal_accept');
|
||||
function success(data, textStatus, jqXHR) {
|
||||
if (data.success === true) {
|
||||
window.location.reload()
|
||||
} else {
|
||||
$('#modal-error').html(data.msg).css('display', 'block');
|
||||
}
|
||||
}).on('click', '#btn-confirm',function () {
|
||||
var $form = $('#form_terminal_accept');
|
||||
function success(data, textStatus, jqXHR) {
|
||||
if (data.success === true) {
|
||||
window.location.reload()
|
||||
} else {
|
||||
$('#modal-error').html(data.msg).css('display', 'block');
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
})
|
||||
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
}).on('click', '.btn-del', function(){
|
||||
var $this = $(this);
|
||||
var id = $this.data('id');
|
||||
|
@ -124,8 +124,7 @@ $(document).ready(function(){
|
|||
var $this = $(this);
|
||||
var terminal_id = $this.data('id');
|
||||
var the_url = "{% url 'api-terminal:terminal-detail' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', terminal_id);
|
||||
var post_url = $('#form_terminal_accept').attr('action').replace('{{ DEFAULT_PK }}', terminal_id);
|
||||
console.log(post_url);
|
||||
var post_url = "{% url 'terminal:terminal-accept' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', terminal_id);
|
||||
$.ajax({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
{% load bootstrap3 %}
|
||||
<form action="{% url 'terminal:terminal-accept' pk="{{ DEFAULT_PK }}" %}" method="post" class="form-horizontal" id="form_terminal_accept" enctype="multipart/form-data">
|
||||
<form action="" method="post" class="form-horizontal" id="form_terminal_accept" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<p class="alert alert-danger" id="modal-error" style="display: none"></p>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
|
@ -16,4 +16,4 @@
|
|||
</form>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_terminal_accept{% endblock %}
|
||||
{% block modal_confirm_id %}btn-confirm{% endblock %}
|
|
@ -10,8 +10,8 @@ from .. import api
|
|||
app_name = 'terminal'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?status', api.StatusViewSet, 'terminal-status')
|
||||
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?sessions', api.SessionViewSet, 'terminal-sessions')
|
||||
router.register(r'v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status')
|
||||
router.register(r'v1/terminal/(?P<terminal>a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
|
||||
router.register(r'v1/tasks', api.TaskViewSet, 'tasks')
|
||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||
router.register(r'v1/command', api.CommandViewSet, 'command')
|
||||
|
|
|
@ -70,7 +70,7 @@ class CommandListView(ListView):
|
|||
'command': self.command,
|
||||
'date_from': self.date_from_s,
|
||||
'date_to': self.date_to_s,
|
||||
'user': self.user,
|
||||
'username': self.user,
|
||||
'asset': self.asset,
|
||||
'system_user': self.system_user,
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ class SessionListView(AdminUserRequiredMixin, ListView):
|
|||
'system_user_list': utils.get_system_user_list_from_cache(),
|
||||
'date_from': self.date_from_s,
|
||||
'date_to': self.date_to_s,
|
||||
'user': self.user,
|
||||
'username': self.user,
|
||||
'asset': self.asset,
|
||||
'system_user': self.system_user,
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ class SessionOfflineListView(SessionListView):
|
|||
class SessionDetailView(SingleObjectMixin, ListView):
|
||||
template_name = 'terminal/session_detail.html'
|
||||
model = Session
|
||||
object = None
|
||||
|
||||
def get_queryset(self):
|
||||
self.object = self.get_object()
|
||||
|
|
|
@ -62,14 +62,14 @@ class TerminalDetailView(LoginRequiredMixin, DetailView):
|
|||
|
||||
class TerminalDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Terminal
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('terminal:terminal-list')
|
||||
|
||||
|
||||
class TerminalAcceptView(AdminUserRequiredMixin, JSONResponseMixin, UpdateView):
|
||||
model = Terminal
|
||||
form_class = TerminalForm
|
||||
template_name = 'Terminal/terminal_modal_test.html'
|
||||
template_name = 'terminal/terminal_modal_accept.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
terminal = form.save()
|
||||
|
|
|
@ -49,5 +49,4 @@ class LoginLog(models.Model):
|
|||
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
||||
|
||||
class Meta:
|
||||
db_table = 'login_log'
|
||||
ordering = ['-datetime', 'username']
|
||||
|
|
|
@ -38,9 +38,9 @@ class User(AbstractUser):
|
|||
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
|
||||
enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP'))
|
||||
secret_key_otp = models.CharField(max_length=16, blank=True)
|
||||
# Todo: private_key may be not used
|
||||
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key'))
|
||||
_public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh public key'))
|
||||
# Todo: Auto generate key, let user download
|
||||
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Private key'))
|
||||
_public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Public key'))
|
||||
comment = models.TextField(max_length=200, blank=True, verbose_name=_('Comment'))
|
||||
is_first_login = models.BooleanField(default=False)
|
||||
date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True, verbose_name=_('Date expired'))
|
||||
|
|
Loading…
Reference in New Issue