mirror of https://github.com/jumpserver/jumpserver
Export login log (#2511)
* [Add]增加登录日志导出功能 * [Update]优化导出登录日志代码 * [Update]优化导出登录日志代码 * [Update]更改导出登录日志按钮pull/2512/head^2
parent
c8aa9d006f
commit
e4e6f59589
|
@ -1,6 +1,7 @@
|
|||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -100,5 +101,22 @@ class UserLoginLog(models.Model):
|
|||
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
||||
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
|
||||
|
||||
@classmethod
|
||||
def get_login_logs(cls, date_form=None, date_to=None, user=None, keyword=None):
|
||||
login_logs = cls.objects.all()
|
||||
if date_form and date_to:
|
||||
login_logs = login_logs.filter(
|
||||
datetime__gt=date_form, datetime__lt=date_to
|
||||
)
|
||||
if user:
|
||||
login_logs = login_logs.filter(username=user)
|
||||
if keyword:
|
||||
login_logs = login_logs.filter(
|
||||
Q(ip__contains=keyword) |
|
||||
Q(city__contains=keyword) |
|
||||
Q(username__contains=keyword)
|
||||
)
|
||||
return login_logs
|
||||
|
||||
class Meta:
|
||||
ordering = ['-datetime', 'username']
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d'}}">
|
||||
<input type="text" id="id_date_from" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d'}}">
|
||||
{# <input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" >#}
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d'}}">
|
||||
<input type="text" id="id_date_to" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d'}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
|
@ -32,7 +32,7 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="{% trans 'Search' %}" value="{{ keyword }}">
|
||||
<input type="text" id="search" class="form-control input-sm" name="keyword" placeholder="{% trans 'Search' %}" value="{{ keyword }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
|
@ -43,38 +43,57 @@
|
|||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<table class="table table-striped table-bordered table-hover " id="login_log_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'UA' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'City' %}</th>
|
||||
<th class="text-center">{% trans 'MFA' %}</th>
|
||||
<th class="text-center">{% trans 'Reason' %}</th>
|
||||
<th class="text-center">{% trans 'Status' %}</th>
|
||||
<th class="text-center">{% trans 'Date' %}</th>
|
||||
</tr>
|
||||
<thead>
|
||||
|
||||
<tbody>
|
||||
{% for login_log in object_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">{{ forloop.counter }}</td>
|
||||
<td class="text-center">{{ login_log.username }}</td>
|
||||
<td class="text-center">{{ login_log.get_type_display }}</td>
|
||||
<td class="text-center">
|
||||
<span href="javascript:void(0);" data-toggle="tooltips" title="{{ login_log.user_agent }}">{{ login_log.user_agent | truncatechars:20 }}</span>
|
||||
</td>
|
||||
<td class="text-center">{{ login_log.ip }}</td>
|
||||
<td class="text-center">{{ login_log.city }}</td>
|
||||
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
||||
<td class="text-center">{{ login_log.get_reason_display }}</td>
|
||||
<td class="text-center">{{ login_log.get_status_display }}</td>
|
||||
<td class="text-center">{{ login_log.datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="" style="margin-top: -20px">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="export">{% trans 'Export login log' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary btn_export">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'UA' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'City' %}</th>
|
||||
<th class="text-center">{% trans 'MFA' %}</th>
|
||||
<th class="text-center">{% trans 'Reason' %}</th>
|
||||
<th class="text-center">{% trans 'Status' %}</th>
|
||||
<th class="text-center">{% trans 'Date' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for login_log in object_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">{{ forloop.counter }}</td>
|
||||
<td class="text-center">{{ login_log.username }}</td>
|
||||
<td class="text-center">{{ login_log.get_type_display }}</td>
|
||||
<td class="text-center">
|
||||
<span href="javascript:void(0);" data-toggle="tooltips" title="{{ login_log.user_agent }}">{{ login_log.user_agent | truncatechars:20 }}</span>
|
||||
</td>
|
||||
<td class="text-center">{{ login_log.ip }}</td>
|
||||
<td class="text-center">{{ login_log.city }}</td>
|
||||
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
||||
<td class="text-center">{{ login_log.get_reason_display }}</td>
|
||||
<td class="text-center">{{ login_log.get_status_display }}</td>
|
||||
<td class="text-center">{{ login_log.datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
|
@ -95,6 +114,29 @@
|
|||
width: 'auto'
|
||||
});
|
||||
})
|
||||
.on('click', '.btn_export', function () {
|
||||
var date_form = $('#id_date_from').val();
|
||||
var date_to = $('#id_date_to').val();
|
||||
var user = $('.select2 option:selected').val();
|
||||
var keyword = $('#search').val();
|
||||
$.ajax({
|
||||
url: "{% url "audits:login-log-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({
|
||||
'date_form':date_form,
|
||||
'date_to':date_to,
|
||||
'user':user,
|
||||
'keyword':keyword
|
||||
}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Export failed');
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -14,4 +14,5 @@ urlpatterns = [
|
|||
path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'),
|
||||
path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'),
|
||||
path('command-execution-log/', views.CommandExecutionListView.as_view(), name='command-execution-log-list'),
|
||||
path('login-log/export/', views.LoginLogExportView.as_view(), name='login-log-export'),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import csv
|
||||
import codecs
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
def get_excel_response(filename):
|
||||
excel_response = HttpResponse(content_type='text/csv')
|
||||
excel_response[
|
||||
'Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
excel_response.write(codecs.BOM_UTF8)
|
||||
return excel_response
|
||||
|
||||
|
||||
def write_content_to_excel(response, header=None, login_logs=None, fields=None):
|
||||
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
||||
if header:
|
||||
writer.writerow(header)
|
||||
if login_logs:
|
||||
for log in login_logs:
|
||||
data = [getattr(log, field.name) for field in fields]
|
||||
writer.writerow(data)
|
||||
return response
|
|
@ -1,8 +1,23 @@
|
|||
import csv
|
||||
import json
|
||||
import uuid
|
||||
import codecs
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import ListView
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import Q
|
||||
|
||||
from audits.utils import get_excel_response, write_content_to_excel
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
|
@ -232,3 +247,38 @@ class CommandExecutionListView(UserCommandExecutionListView):
|
|||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class LoginLogExportView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request):
|
||||
fields = [
|
||||
field for field in UserLoginLog._meta.fields
|
||||
]
|
||||
filename = 'login-logs-{}.csv'.format(
|
||||
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')
|
||||
)
|
||||
excel_response = get_excel_response(filename)
|
||||
header = [field.verbose_name for field in fields]
|
||||
login_logs = cache.get(request.GET.get('spm', ''), [])
|
||||
|
||||
response = write_content_to_excel(excel_response, login_logs=login_logs,
|
||||
header=header, fields=fields)
|
||||
return response
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
date_form = json.loads(request.body).get('date_form', [])
|
||||
date_to = json.loads(request.body).get('date_to', [])
|
||||
user = json.loads(request.body).get('user', [])
|
||||
keyword = json.loads(request.body).get('keyword', [])
|
||||
|
||||
login_logs = UserLoginLog.get_login_logs(
|
||||
date_form=date_form, date_to=date_to, user=user, keyword=keyword)
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
spm = uuid.uuid4().hex
|
||||
cache.set(spm, login_logs, 300)
|
||||
url = reverse('audits:login-log-export') + '?spm=%s' % spm
|
||||
return JsonResponse({'redirect': url})
|
Loading…
Reference in New Issue