mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-26 15:01:59 +00:00
[Feature] 完成登陆日志
This commit is contained in:
parent
a5f9735906
commit
ec8106e43d
@ -94,7 +94,6 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||||||
for asset in assets:
|
for asset in assets:
|
||||||
last_login = self.session_week.filter(asset=asset["asset"]).order_by('date_start').last()
|
last_login = self.session_week.filter(asset=asset["asset"]).order_by('date_start').last()
|
||||||
asset['last'] = last_login
|
asset['last'] = last_login
|
||||||
print(asset)
|
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
def get_week_top10_user(self):
|
def get_week_top10_user(self):
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<ul class="nav nav-second-level active">
|
<ul class="nav nav-second-level active">
|
||||||
<li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User' %}</a></li>
|
<li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User' %}</a></li>
|
||||||
<li id="user-group"><a href="{% url 'users:user-group-list' %}">{% trans 'User group' %}</a></li>
|
<li id="user-group"><a href="{% url 'users:user-group-list' %}">{% trans 'User group' %}</a></li>
|
||||||
<li id="user-group"><a href="{% url 'users:user-group-list' %}">{% trans 'Login logs' %}</a></li>
|
<li id="login-log"><a href="{% url 'users:login-log-list' %}">{% trans 'Login logs' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li id="assets">
|
<li id="assets">
|
||||||
|
@ -164,9 +164,8 @@ class UserAuthApi(APIView):
|
|||||||
if user:
|
if user:
|
||||||
token = generate_token(request, user)
|
token = generate_token(request, user)
|
||||||
write_login_log_async.delay(
|
write_login_log_async.delay(
|
||||||
user.username, name=user.name,
|
user.username, ip=login_ip,
|
||||||
user_agent=user_agent, login_ip=login_ip,
|
type=login_type, user_agent=user_agent,
|
||||||
login_type=login_type
|
|
||||||
)
|
)
|
||||||
return Response({'token': token, 'user': user.to_json()})
|
return Response({'token': token, 'user': user.to_json()})
|
||||||
else:
|
else:
|
||||||
|
@ -16,8 +16,7 @@ class AccessKey(models.Model):
|
|||||||
default=uuid.uuid4, editable=False)
|
default=uuid.uuid4, editable=False)
|
||||||
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
||||||
default=uuid.uuid4, editable=False)
|
default=uuid.uuid4, editable=False)
|
||||||
user = models.ForeignKey(User, verbose_name='User',
|
user = models.ForeignKey(User, verbose_name='User', on_delete=models.CASCADE, related_name='access_key')
|
||||||
related_name='access_key')
|
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return str(self.id)
|
return str(self.id)
|
||||||
@ -39,8 +38,7 @@ class PrivateToken(Token):
|
|||||||
class LoginLog(models.Model):
|
class LoginLog(models.Model):
|
||||||
LOGIN_TYPE_CHOICE = (
|
LOGIN_TYPE_CHOICE = (
|
||||||
('W', 'Web'),
|
('W', 'Web'),
|
||||||
('ST', 'SSH Terminal'),
|
('T', 'Terminal'),
|
||||||
('WT', 'Web Terminal')
|
|
||||||
)
|
)
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
||||||
|
92
apps/users/templates/users/login_log_list.html
Normal file
92
apps/users/templates/users/login_log_list.html
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{% extends '_base_list.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% load common_tags %}
|
||||||
|
{% block content_left_head %}
|
||||||
|
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
#search_btn {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block table_search %}
|
||||||
|
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||||
|
<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 }}">
|
||||||
|
<span class="input-group-addon">to</span>
|
||||||
|
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<select class="select2 form-control" name="username">
|
||||||
|
<option value="">{% trans 'Select user' %}</option>
|
||||||
|
{% for u in user_list %}
|
||||||
|
<option value="{{ u }}" {% if u == username %} selected {% endif %}>{{ u }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-btn">
|
||||||
|
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||||
|
搜索
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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 '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.datetime }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('table').DataTable({
|
||||||
|
"searching": false,
|
||||||
|
"bInfo" : false,
|
||||||
|
"paging": false,
|
||||||
|
"order": []
|
||||||
|
});
|
||||||
|
$('#date .input-daterange').datepicker({
|
||||||
|
dateFormat: 'mm/dd/yy',
|
||||||
|
keyboardNavigation: false,
|
||||||
|
forceParse: false,
|
||||||
|
autoclose: true
|
||||||
|
});
|
||||||
|
$('.select2').select2();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -43,4 +43,7 @@ urlpatterns = [
|
|||||||
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission$', views.UserGroupAssetPermissionView.as_view(), name='user-group-asset-permission'),
|
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission$', views.UserGroupAssetPermissionView.as_view(), name='user-group-asset-permission'),
|
||||||
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission/create$', views.UserGroupAssetPermissionCreateView.as_view(), name='user-group-asset-permission-create'),
|
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission/create$', views.UserGroupAssetPermissionCreateView.as_view(), name='user-group-asset-permission-create'),
|
||||||
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]+)/assets', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
|
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]+)/assets', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
|
||||||
|
|
||||||
|
# Login log
|
||||||
|
url(r'^login-log/$', views.LoginLogListView.as_view(), name='login-log-list'),
|
||||||
]
|
]
|
||||||
|
@ -9,7 +9,7 @@ import requests
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate, login as auth_login
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
@ -5,7 +5,9 @@ from django import forms
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.views.generic import ListView
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
|
from django.db.models import Q
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import reverse, redirect
|
from django.shortcuts import reverse, redirect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
@ -17,9 +19,10 @@ from django.views.generic.base import TemplateView
|
|||||||
from django.views.generic.edit import FormView
|
from django.views.generic.edit import FormView
|
||||||
from formtools.wizard.views import SessionWizardView
|
from formtools.wizard.views import SessionWizardView
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from ..models import User
|
from ..models import User, LoginLog
|
||||||
from ..utils import send_reset_password_mail
|
from ..utils import send_reset_password_mail
|
||||||
from ..tasks import write_login_log_async
|
from ..tasks import write_login_log_async
|
||||||
from .. import forms
|
from .. import forms
|
||||||
@ -28,7 +31,7 @@ from .. import forms
|
|||||||
__all__ = ['UserLoginView', 'UserLogoutView',
|
__all__ = ['UserLoginView', 'UserLogoutView',
|
||||||
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
||||||
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
||||||
'UserFirstLoginView']
|
'UserFirstLoginView', 'LoginLogListView']
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
||||||
@ -48,10 +51,10 @@ class UserLoginView(FormView):
|
|||||||
auth_login(self.request, form.get_user())
|
auth_login(self.request, form.get_user())
|
||||||
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
||||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||||
write_login_log_async.delay(self.request.user.username,
|
write_login_log_async.delay(
|
||||||
self.request.user.name,
|
self.request.user.username, type='W',
|
||||||
login_type='W', login_ip=login_ip,
|
ip=login_ip, user_agent=user_agent
|
||||||
user_agent=user_agent)
|
)
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -202,3 +205,60 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
|||||||
|
|
||||||
form.instance = self.request.user
|
form.instance = self.request.user
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
|
class LoginLogListView(ListView):
|
||||||
|
template_name = 'users/login_log_list.html'
|
||||||
|
model = LoginLog
|
||||||
|
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||||
|
username = keyword = date_from_s = date_to_s = ""
|
||||||
|
date_format = '%m/%d/%Y'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
date_to_default = timezone.now()
|
||||||
|
date_from_default = timezone.now() - timezone.timedelta(7)
|
||||||
|
date_to_default_s = date_to_default.strftime(self.date_format)
|
||||||
|
date_from_default_s = date_from_default.strftime(self.date_format)
|
||||||
|
|
||||||
|
self.username = self.request.GET.get('username', '')
|
||||||
|
self.keyword = self.request.GET.get("keyword", '')
|
||||||
|
self.date_from_s = self.request.GET.get('date_from', date_from_default_s)
|
||||||
|
self.date_to_s = self.request.GET.get('date_to', date_to_default_s)
|
||||||
|
|
||||||
|
self.queryset = super().get_queryset()
|
||||||
|
if self.username:
|
||||||
|
self.queryset = self.queryset.filter(username=self.username)
|
||||||
|
if self.date_from_s:
|
||||||
|
date_from = timezone.datetime.strptime(self.date_from_s, '%m/%d/%Y')
|
||||||
|
date_from = date_from.replace(
|
||||||
|
tzinfo=timezone.get_current_timezone()
|
||||||
|
)
|
||||||
|
self.queryset = self.queryset.filter(datetime__gt=date_from)
|
||||||
|
if self.date_to_s:
|
||||||
|
date_to = timezone.datetime.strptime(
|
||||||
|
self.date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S'
|
||||||
|
)
|
||||||
|
date_to = date_to.replace(
|
||||||
|
tzinfo=timezone.get_current_timezone()
|
||||||
|
)
|
||||||
|
self.queryset = self.queryset.filter(datetime__lt=date_to)
|
||||||
|
if self.keyword:
|
||||||
|
self.queryset = self.queryset.filter(
|
||||||
|
Q(ip__contains=self.keyword) |
|
||||||
|
Q(city__contains=self.keyword) |
|
||||||
|
Q(username__contains=self.keyword)
|
||||||
|
)
|
||||||
|
return self.queryset
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'app': _('Users'),
|
||||||
|
'action': _('Login log list'),
|
||||||
|
'date_from': self.date_from_s,
|
||||||
|
'date_to': self.date_to_s,
|
||||||
|
'username': self.username,
|
||||||
|
'keyword': self.keyword,
|
||||||
|
'user_list': set(LoginLog.objects.all().values_list('username', flat=True))
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
@ -1,4 +1,4 @@
|
|||||||
Django>=1.11
|
Django==1.11
|
||||||
django-bootstrap3>=8.2.2
|
django-bootstrap3>=8.2.2
|
||||||
Pillow>=4.1.0
|
Pillow>=4.1.0
|
||||||
djangorestframework>=3.6.2
|
djangorestframework>=3.6.2
|
||||||
|
Loading…
Reference in New Issue
Block a user