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__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
class AssetCreateForm(forms.ModelForm):
|
class AssetCreateForm(forms.ModelForm):
|
||||||
|
|
||||||
|
@ -240,7 +241,7 @@ class SystemUserForm(forms.ModelForm):
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'username', 'protocol', 'auto_generate_key',
|
'name', 'username', 'protocol', 'auto_generate_key',
|
||||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||||
'comment', 'shell', 'cluster'
|
'comment', 'shell', 'cluster', 'priority',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||||
|
@ -254,6 +255,7 @@ class SystemUserForm(forms.ModelForm):
|
||||||
'username': '* required',
|
'username': '* required',
|
||||||
'cluster': 'If auto push checked, system user will be create at cluster assets',
|
'cluster': 'If auto push checked, system user will be create at cluster assets',
|
||||||
'auto_push': 'Auto push system user to asset',
|
'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:
|
class Meta:
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'username', 'protocol',
|
'name', 'username', 'protocol', 'priority',
|
||||||
'sudo', 'comment', 'shell', 'cluster'
|
'sudo', 'comment', 'shell', 'cluster'
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
|
@ -275,6 +277,7 @@ class SystemUserUpdateForm(forms.ModelForm):
|
||||||
'name': '* required',
|
'name': '* required',
|
||||||
'username': '* required',
|
'username': '* required',
|
||||||
'cluster': 'If auto push checked, then push system user to that cluster assets',
|
'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
|
import uuid
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
|
||||||
|
import sshpubkeys
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
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, ])
|
_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'))
|
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
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'))
|
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -45,16 +47,21 @@ class AssetUser(models.Model):
|
||||||
@property
|
@property
|
||||||
def private_key(self):
|
def private_key(self):
|
||||||
if self._private_key:
|
if self._private_key:
|
||||||
key_str = signer.unsign(self._private_key)
|
return signer.unsign(self._private_key)
|
||||||
return ssh_key_string_to_obj(key_str, password=self.password)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@private_key.setter
|
@private_key.setter
|
||||||
def private_key(self, private_key_raw):
|
def private_key(self, private_key_raw):
|
||||||
raise AttributeError("Using set_auth do that")
|
raise AttributeError("Using set_auth do that")
|
||||||
# self._private_key = signer.sign(private_key_raw)
|
# 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
|
@property
|
||||||
def private_key_file(self):
|
def private_key_file(self):
|
||||||
if not self.private_key:
|
if not self.private_key:
|
||||||
|
@ -74,6 +81,15 @@ class AssetUser(models.Model):
|
||||||
def public_key(self):
|
def public_key(self):
|
||||||
return signer.unsign(self._public_key)
|
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):
|
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||||
update_fields = []
|
update_fields = []
|
||||||
if password:
|
if password:
|
||||||
|
@ -170,6 +186,7 @@ class SystemUser(AssetUser):
|
||||||
('K', 'Public key'),
|
('K', 'Public key'),
|
||||||
)
|
)
|
||||||
cluster = models.ManyToManyField('assets.Cluster', verbose_name=_("Cluster"))
|
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'))
|
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||||
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
|
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
|
||||||
|
@ -205,6 +222,7 @@ class SystemUser(AssetUser):
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'username': self.username,
|
'username': self.username,
|
||||||
'protocol': self.protocol,
|
'protocol': self.protocol,
|
||||||
|
'priority': self.priority,
|
||||||
'auto_push': self.auto_push,
|
'auto_push': self.auto_push,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
||||||
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
fields = ('id', 'name', 'username', 'protocol', 'comment')
|
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
|
||||||
|
|
||||||
|
|
||||||
class SystemUserUpdateAssetsSerializer(serializers.ModelSerializer):
|
class SystemUserUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<h3>{% trans 'Basic' %}</h3>
|
<h3>{% trans 'Basic' %}</h3>
|
||||||
{% bootstrap_field form.name layout="horizontal" %}
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
{% bootstrap_field form.username layout="horizontal" %}
|
{% bootstrap_field form.username layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.priority layout="horizontal" %}
|
||||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||||
{% bootstrap_field form.cluster layout="horizontal" %}
|
{% bootstrap_field form.cluster layout="horizontal" %}
|
||||||
|
|
||||||
|
@ -49,7 +50,6 @@
|
||||||
{{ form.auto_generate_key}}
|
{{ form.auto_generate_key}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-fields">
|
<div class="auth-fields">
|
||||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||||
|
|
|
@ -64,7 +64,9 @@
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2();
|
$('.select2').select2({
|
||||||
|
allowClear: true
|
||||||
|
});
|
||||||
$("#tags").select2({
|
$("#tags").select2({
|
||||||
tags: true,
|
tags: true,
|
||||||
maximumSelectionLength: 8 //最多能够选择的个数
|
maximumSelectionLength: 8 //最多能够选择的个数
|
||||||
|
|
|
@ -123,7 +123,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||||
|
|
||||||
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
model = AdminUser
|
model = AdminUser
|
||||||
template_name = 'assets/delete_confirm.html'
|
template_name = 'delete_confirm.html'
|
||||||
success_url = reverse_lazy('assets:admin-user-list')
|
success_url = reverse_lazy('assets:admin-user-list')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,13 @@ import chardet
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured, FieldDoesNotExist
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.views.generic import TemplateView, ListView, View
|
from django.views.generic import TemplateView, ListView, View
|
||||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
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.views.decorators.csrf import csrf_protect, csrf_exempt
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
@ -22,8 +23,10 @@ from django.utils import timezone
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.shortcuts import get_object_or_404, redirect, reverse
|
from django.shortcuts import get_object_or_404, redirect, reverse
|
||||||
|
|
||||||
|
|
||||||
from common.mixins import JSONResponseMixin
|
from common.mixins import JSONResponseMixin
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
|
from common.imexp import ModelExportView
|
||||||
from .. import forms
|
from .. import forms
|
||||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||||
from ..hands import AdminUserRequiredMixin
|
from ..hands import AdminUserRequiredMixin
|
||||||
|
@ -169,7 +172,7 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
|
|
||||||
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
model = Asset
|
model = Asset
|
||||||
template_name = 'assets/delete_confirm.html'
|
template_name = 'delete_confirm.html'
|
||||||
success_url = reverse_lazy('assets:asset-list')
|
success_url = reverse_lazy('assets:asset-list')
|
||||||
|
|
||||||
|
|
||||||
|
@ -193,46 +196,11 @@ class AssetDetailView(DetailView):
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
class AssetExportView(View):
|
class AssetExportView(ModelExportView):
|
||||||
def get(self, request):
|
filename_prefix = 'jumpserver'
|
||||||
spm = request.GET.get('spm', '')
|
redirect_url = reverse_lazy('assets:asset-export')
|
||||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [1]
|
model = Asset
|
||||||
assets_id = cache.get(spm, assets_id_default)
|
fields = ('hostname', 'ip')
|
||||||
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 BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
|
|
|
@ -97,5 +97,5 @@ class ClusterAssetsView(AdminUserRequiredMixin, DetailView):
|
||||||
|
|
||||||
class ClusterDeleteView(AdminUserRequiredMixin, DeleteView):
|
class ClusterDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
model = Cluster
|
model = Cluster
|
||||||
template_name = 'assets/delete_confirm.html'
|
template_name = 'delete_confirm.html'
|
||||||
success_url = reverse_lazy('assets:cluster-list')
|
success_url = reverse_lazy('assets:cluster-list')
|
||||||
|
|
|
@ -101,6 +101,6 @@ class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
|
|
||||||
|
|
||||||
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
|
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
template_name = 'assets/delete_confirm.html'
|
template_name = 'delete_confirm.html'
|
||||||
model = AssetGroup
|
model = AssetGroup
|
||||||
success_url = reverse_lazy('assets:asset-group-list')
|
success_url = reverse_lazy('assets:asset-group-list')
|
||||||
|
|
|
@ -99,7 +99,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
|
|
||||||
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
template_name = 'assets/delete_confirm.html'
|
template_name = 'delete_confirm.html'
|
||||||
success_url = reverse_lazy('assets:system-user-list')
|
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"
|
msgid "Location"
|
||||||
msgstr "位置"
|
msgstr "位置"
|
||||||
|
|
||||||
#: assets/templates/assets/delete_confirm.html:6
|
#: assets/templates/delete_confirm.html:6
|
||||||
#: perms/templates/perms/delete_confirm.html:6
|
#: perms/templates/delete_confirm.html:6
|
||||||
#: users/templates/users/user_delete_confirm.html:6
|
#: users/templates/users/user_delete_confirm.html:6
|
||||||
msgid "Confirm delete"
|
msgid "Confirm delete"
|
||||||
msgstr "确认删除"
|
msgstr "确认删除"
|
||||||
|
|
||||||
#: assets/templates/assets/delete_confirm.html:11
|
#: assets/templates/delete_confirm.html:11
|
||||||
msgid "Are you sure delete"
|
msgid "Are you sure delete"
|
||||||
msgstr "您确定删除吗?"
|
msgstr "您确定删除吗?"
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
|
||||||
Asset, AssetGroup
|
Asset, AssetGroup
|
||||||
from .models import AssetPermission
|
from .models import AssetPermission
|
||||||
from .forms import AssetPermissionForm
|
from .forms import AssetPermissionForm
|
||||||
from .utils import associate_system_users_and_assets
|
# from .utils import associate_system_users_and_assets
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
||||||
|
@ -87,15 +87,15 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
|
||||||
'successfully.'.format(url=url, name=self.object.name))
|
'successfully.'.format(url=url, name=self.object.name))
|
||||||
return success_message
|
return success_message
|
||||||
|
|
||||||
def form_valid(self, form):
|
# Todo: When create push system user
|
||||||
assets = form.cleaned_data['assets']
|
# def form_valid(self, form):
|
||||||
asset_groups = form.cleaned_data['asset_groups']
|
# assets = form.cleaned_data['assets']
|
||||||
system_users = form.cleaned_data['system_users']
|
# asset_groups = form.cleaned_data['asset_groups']
|
||||||
associate_system_users_and_assets(system_users, assets, asset_groups)
|
# system_users = form.cleaned_data['system_users']
|
||||||
response = super(AssetPermissionCreateView, self).form_valid(form)
|
# response = super(AssetPermissionCreateView, self).form_valid(form)
|
||||||
self.object.created_by = self.request.user.name
|
# self.object.created_by = self.request.user.name
|
||||||
self.object.save()
|
# self.object.save()
|
||||||
return response
|
# return response
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
|
class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
|
@ -150,7 +150,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
|
|
||||||
class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView):
|
class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
model = AssetPermission
|
model = AssetPermission
|
||||||
template_name = 'perms/delete_confirm.html'
|
template_name = 'delete_confirm.html'
|
||||||
success_url = reverse_lazy('perms:asset-permission-list')
|
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 i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load common_tags %}
|
{% 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/footable/footable.core.css" %}" rel="stylesheet">
|
||||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
|
@ -12,6 +12,10 @@
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_left_head %}
|
||||||
|
123
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||||
<div class="form-group" id="date">
|
<div class="form-group" id="date">
|
||||||
|
@ -26,7 +30,7 @@
|
||||||
<select class="select2 form-control" name="username">
|
<select class="select2 form-control" name="username">
|
||||||
<option value="">{% trans 'User' %}</option>
|
<option value="">{% trans 'User' %}</option>
|
||||||
{% for u in user_list %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load terminal_tags %}
|
{% load terminal_tags %}
|
||||||
{% block content_left_head %}
|
{% block custom_head_css_js %}
|
||||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
#search_btn {
|
#search_btn {
|
||||||
|
@ -11,6 +11,9 @@
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_left_head %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||||
|
@ -26,7 +29,7 @@
|
||||||
<select class="select2 form-control" name="user">
|
<select class="select2 form-control" name="user">
|
||||||
<option value="">{% trans 'User' %}</option>
|
<option value="">{% trans 'User' %}</option>
|
||||||
{% for u in user_list %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,10 +109,17 @@
|
||||||
function success() {
|
function success() {
|
||||||
window.setTimeout(function () {
|
window.setTimeout(function () {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}, 300)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
var success_message = '{% trans "Terminate task send, waiting ..." %}';
|
||||||
var the_url = "{% url 'api-terminal:tasks-list' %}";
|
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() {
|
$(document).ready(function() {
|
||||||
$('table').DataTable({
|
$('table').DataTable({
|
||||||
|
@ -118,7 +128,9 @@
|
||||||
"bInfo" : false,
|
"bInfo" : false,
|
||||||
"order": []
|
"order": []
|
||||||
});
|
});
|
||||||
$('.select2').select2();
|
$('.select2').select2({
|
||||||
|
dropdownAutoWidth: true
|
||||||
|
});
|
||||||
$('#date .input-daterange').datepicker({
|
$('#date .input-daterange').datepicker({
|
||||||
dateFormat: 'mm/dd/yy',
|
dateFormat: 'mm/dd/yy',
|
||||||
keyboardNavigation: false,
|
keyboardNavigation: false,
|
||||||
|
@ -135,12 +147,6 @@
|
||||||
terminal: terminal_id
|
terminal: terminal_id
|
||||||
};
|
};
|
||||||
terminateSession(data)
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block custom_head_css_js %}
|
{% block custom_head_css_js %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div.dataTables_wrapper div.dataTables_filter,
|
div.dataTables_wrapper div.dataTables_filter,
|
||||||
.dataTables_length {
|
.dataTables_length {
|
||||||
|
@ -15,9 +14,10 @@
|
||||||
#modal .modal-body { max-height: 200px; }
|
#modal .modal-body { max-height: 200px; }
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}{% endblock %}
|
{% block table_search %}{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% 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" >
|
<table class="table table-striped table-bordered table-hover " id="terminal_list_table" >
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -40,12 +40,11 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% include 'terminal/terminal_modal_accept.html' %}
|
{% include 'terminal/terminal_modal_accept.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function(){
|
function initTable() {
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#terminal_list_table'),
|
ele: $('#terminal_list_table'),
|
||||||
buttons: [],
|
buttons: [],
|
||||||
|
@ -100,19 +99,20 @@ $(document).ready(function(){
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initDataTable(options);
|
||||||
|
}
|
||||||
|
$(document).ready(function(){
|
||||||
|
initTable();
|
||||||
|
|
||||||
$('#btn_terminal_accept').click(function () {
|
}).on('click', '#btn-confirm',function () {
|
||||||
var $form = $('#form_terminal_accept');
|
var $form = $('#form_terminal_accept');
|
||||||
function success(data, textStatus, jqXHR) {
|
function success(data, textStatus, jqXHR) {
|
||||||
if (data.success === true) {
|
if (data.success === true) {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
} else {
|
} else {
|
||||||
$('#modal-error').html(data.msg).css('display', 'block');
|
$('#modal-error').html(data.msg).css('display', 'block');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$form.ajaxSubmit({success: success});
|
}
|
||||||
})
|
$form.ajaxSubmit({success: success});
|
||||||
|
|
||||||
}).on('click', '.btn-del', function(){
|
}).on('click', '.btn-del', function(){
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var id = $this.data('id');
|
var id = $this.data('id');
|
||||||
|
@ -124,8 +124,7 @@ $(document).ready(function(){
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var terminal_id = $this.data('id');
|
var terminal_id = $this.data('id');
|
||||||
var the_url = "{% url 'api-terminal:terminal-detail' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', terminal_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);
|
var post_url = "{% url 'terminal:terminal-accept' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', terminal_id);
|
||||||
console.log(post_url);
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
|
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
|
||||||
{% block modal_body %}
|
{% block modal_body %}
|
||||||
{% load bootstrap3 %}
|
{% 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 %}
|
{% csrf_token %}
|
||||||
<p class="alert alert-danger" id="modal-error" style="display: none"></p>
|
<p class="alert alert-danger" id="modal-error" style="display: none"></p>
|
||||||
{% bootstrap_field form.name layout="horizontal" %}
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
|
@ -16,4 +16,4 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% 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'
|
app_name = 'terminal'
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?status', api.StatusViewSet, 'terminal-status')
|
router.register(r'v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?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})?/?sessions', api.SessionViewSet, 'terminal-sessions')
|
||||||
router.register(r'v1/tasks', api.TaskViewSet, 'tasks')
|
router.register(r'v1/tasks', api.TaskViewSet, 'tasks')
|
||||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||||
router.register(r'v1/command', api.CommandViewSet, 'command')
|
router.register(r'v1/command', api.CommandViewSet, 'command')
|
||||||
|
|
|
@ -70,7 +70,7 @@ class CommandListView(ListView):
|
||||||
'command': self.command,
|
'command': self.command,
|
||||||
'date_from': self.date_from_s,
|
'date_from': self.date_from_s,
|
||||||
'date_to': self.date_to_s,
|
'date_to': self.date_to_s,
|
||||||
'user': self.user,
|
'username': self.user,
|
||||||
'asset': self.asset,
|
'asset': self.asset,
|
||||||
'system_user': self.system_user,
|
'system_user': self.system_user,
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ class SessionListView(AdminUserRequiredMixin, ListView):
|
||||||
'system_user_list': utils.get_system_user_list_from_cache(),
|
'system_user_list': utils.get_system_user_list_from_cache(),
|
||||||
'date_from': self.date_from_s,
|
'date_from': self.date_from_s,
|
||||||
'date_to': self.date_to_s,
|
'date_to': self.date_to_s,
|
||||||
'user': self.user,
|
'username': self.user,
|
||||||
'asset': self.asset,
|
'asset': self.asset,
|
||||||
'system_user': self.system_user,
|
'system_user': self.system_user,
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,7 @@ class SessionOfflineListView(SessionListView):
|
||||||
class SessionDetailView(SingleObjectMixin, ListView):
|
class SessionDetailView(SingleObjectMixin, ListView):
|
||||||
template_name = 'terminal/session_detail.html'
|
template_name = 'terminal/session_detail.html'
|
||||||
model = Session
|
model = Session
|
||||||
|
object = None
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
|
|
|
@ -62,14 +62,14 @@ class TerminalDetailView(LoginRequiredMixin, DetailView):
|
||||||
|
|
||||||
class TerminalDeleteView(AdminUserRequiredMixin, DeleteView):
|
class TerminalDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
model = Terminal
|
model = Terminal
|
||||||
template_name = 'assets/delete_confirm.html'
|
template_name = 'delete_confirm.html'
|
||||||
success_url = reverse_lazy('terminal:terminal-list')
|
success_url = reverse_lazy('terminal:terminal-list')
|
||||||
|
|
||||||
|
|
||||||
class TerminalAcceptView(AdminUserRequiredMixin, JSONResponseMixin, UpdateView):
|
class TerminalAcceptView(AdminUserRequiredMixin, JSONResponseMixin, UpdateView):
|
||||||
model = Terminal
|
model = Terminal
|
||||||
form_class = TerminalForm
|
form_class = TerminalForm
|
||||||
template_name = 'Terminal/terminal_modal_test.html'
|
template_name = 'terminal/terminal_modal_accept.html'
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
terminal = form.save()
|
terminal = form.save()
|
||||||
|
|
|
@ -49,5 +49,4 @@ class LoginLog(models.Model):
|
||||||
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'login_log'
|
|
||||||
ordering = ['-datetime', 'username']
|
ordering = ['-datetime', 'username']
|
||||||
|
|
|
@ -38,9 +38,9 @@ class User(AbstractUser):
|
||||||
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
|
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
|
||||||
enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP'))
|
enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP'))
|
||||||
secret_key_otp = models.CharField(max_length=16, blank=True)
|
secret_key_otp = models.CharField(max_length=16, blank=True)
|
||||||
# Todo: private_key may be not used
|
# Todo: Auto generate key, let user download
|
||||||
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key'))
|
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Private key'))
|
||||||
_public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh public 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'))
|
comment = models.TextField(max_length=200, blank=True, verbose_name=_('Comment'))
|
||||||
is_first_login = models.BooleanField(default=False)
|
is_first_login = models.BooleanField(default=False)
|
||||||
date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True, verbose_name=_('Date expired'))
|
date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True, verbose_name=_('Date expired'))
|
||||||
|
|
Loading…
Reference in New Issue