mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-30 04:25:47 +00:00
new file access log page
This commit is contained in:
parent
bda3595d6f
commit
643d036942
@ -1198,6 +1198,7 @@ textarea:-moz-placeholder {/* for FF */
|
||||
.tabnav,
|
||||
.repo-file-list-topbar,
|
||||
.commit-list-topbar,
|
||||
.file-audit-list-topbar,
|
||||
#dir-view .repo-op,
|
||||
.wiki-top {
|
||||
padding:9px 10px;
|
||||
@ -1219,6 +1220,7 @@ textarea:-moz-placeholder {/* for FF */
|
||||
position:relative;
|
||||
}
|
||||
.commit-list-topbar,
|
||||
.file-audit-list-topbar,
|
||||
.repo-file-list-topbar {
|
||||
margin-bottom:0;
|
||||
}
|
||||
@ -1822,6 +1824,7 @@ textarea:-moz-placeholder {/* for FF */
|
||||
margin: 2px 0 5px;
|
||||
}
|
||||
.repo-file-list-outer-container,
|
||||
.file-audit-list-outer-container,
|
||||
.commit-list-outer-container {
|
||||
padding:3px;
|
||||
background:#eee;
|
||||
@ -1829,6 +1832,7 @@ textarea:-moz-placeholder {/* for FF */
|
||||
margin:10px 0 25px;
|
||||
}
|
||||
.repo-file-list-inner-container,
|
||||
.file-audit-list-inner-container,
|
||||
.commit-list-inner-container {
|
||||
min-height:250px;
|
||||
background:#fff;
|
||||
@ -1838,10 +1842,12 @@ textarea:-moz-placeholder {/* for FF */
|
||||
.repo-file-list-not-show {
|
||||
padding-left:10px;
|
||||
}
|
||||
.file-audit-list-inner-container .file-audit-list-topbar,
|
||||
.commit-list-inner-container .commit-list-topbar,
|
||||
.repo-file-list-inner-container .repo-file-list-topbar {
|
||||
padding:8px 10px;
|
||||
}
|
||||
.file-audit-list-topbar .path,
|
||||
.commit-list-topbar .path,
|
||||
.repo-file-list-topbar .path {
|
||||
font-size:14px;
|
||||
@ -2181,12 +2187,15 @@ textarea:-moz-placeholder {/* for FF */
|
||||
#back {
|
||||
margin-top:3px;
|
||||
}
|
||||
.file-audit-list,
|
||||
.commit-list {
|
||||
margin:0 0 20px;
|
||||
}
|
||||
.file-audit-list .user,
|
||||
.commit-list .time {
|
||||
padding-left:10px;
|
||||
}
|
||||
.file-audit-list .avatar,
|
||||
.commit-list .avatar {
|
||||
border-radius:2px;
|
||||
}
|
||||
|
91
seahub/templates/file_access.html
Normal file
91
seahub/templates/file_access.html
Normal file
@ -0,0 +1,91 @@
|
||||
{% extends base_template %}
|
||||
{% load seahub_tags avatar_tags i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block sub_title %}{% trans "Access Log" %} - {% endblock %}
|
||||
|
||||
{% block main_panel %}
|
||||
<h2><span class="op-target">{{ filename }}</span> {% trans "Access Log" %}</h2>
|
||||
<div class="file-audit-list-outer-container">
|
||||
<div class="file-audit-list-inner-container">
|
||||
<div class="file-audit-list-topbar ovhd">
|
||||
<p class="path fleft">
|
||||
{% trans 'Current Path:' %}
|
||||
{% for name, link in zipped %}
|
||||
{% if not forloop.last %}
|
||||
<a href="{% url 'view_common_lib_dir' repo.id link|urlencode|strip_slash %}">{{ name }}</a> /
|
||||
{% else %}
|
||||
<a href="{% url 'view_lib_file' repo.id path|urlencode %}" target="_blank" >{{ name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if events %}
|
||||
<table class='file-audit-list'>
|
||||
<tr>
|
||||
<th width="25%" class="user">{% trans "User" %}</th>
|
||||
<th width="20%">{% trans "Type" %}</th>
|
||||
<th width="30%">{% trans "IP / Device" %}</th>
|
||||
<th width="25%">{% trans "Date" %}</th>
|
||||
</tr>
|
||||
|
||||
{% for e in events %}
|
||||
<tr>
|
||||
<td class="user">
|
||||
{% avatar e.user 16 %}
|
||||
{% if e.user %}
|
||||
<a href="{% url 'user_profile' e.user %}">{{ e.user }}</a>
|
||||
{% else %}
|
||||
<span>{% trans "Anonymous User" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span>{{ e.event_type }}</span></td>
|
||||
<td>
|
||||
{% if e.show_device %}
|
||||
{{ e.ip }} / {{ e.show_device }}
|
||||
{% else %}
|
||||
{{ e.ip }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ e.time|date:"Y-m-d G:i:s" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div id="paginator">
|
||||
{% if current_page != 1 %}
|
||||
<a href="?page={{ prev_page }}&per_page={{ per_page }}{{ extra_href }}">{% trans "Previous" %}</a>
|
||||
{% endif %}
|
||||
{% if page_next %}
|
||||
<a href="?page={{ next_page }}&per_page={{ per_page }}{{ extra_href }}">{% trans "Next" %}</a>
|
||||
{% endif %}
|
||||
{% if current_page != 1 or page_next %}
|
||||
|
|
||||
{% endif %}
|
||||
<span>{% trans "Per page: " %}</span>
|
||||
{% if per_page == 25 %}
|
||||
<span> 25 </span>
|
||||
{% else %}
|
||||
<a href="?per_page=25{{ extra_href }}" class="per-page">25</a>
|
||||
{% endif %}
|
||||
{% if per_page == 50 %}
|
||||
<span> 50 </span>
|
||||
{% else %}
|
||||
<a href="?per_page=50{{ extra_href }}" class="per-page">50</a>
|
||||
{% endif %}
|
||||
{% if per_page == 100 %}
|
||||
<span> 100 </span>
|
||||
{% else %}
|
||||
<a href="?per_page=100{{ extra_href }}" class="per-page">100</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-tips">
|
||||
<h2 class="alc">{% trans "No file access infomation" %}</h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -296,6 +296,7 @@
|
||||
<li><a class="op cp" href="#">{% trans "Copy" %}</a></li>
|
||||
<li><a class="op file-history" href="{{ SITE_ROOT }}repo/file_revisions/<%= repo_id %>/?p=<% print(encodeURIComponent(dirent_path)); %>" target="_blank">{% trans "History" %}</a></li>
|
||||
<% if (is_pro) { %>
|
||||
<li><a class="op" href="{{ SITE_ROOT }}repo/file-access/<%= repo_id %>/?p=<% print(encodeURIComponent(dirent_path)); %>" target="_blank">{% trans "Access Log" %}</a></li>
|
||||
<% if (dirent.is_locked) { %>
|
||||
<% if (dirent.locked_by_me) { %>
|
||||
<li><a class="op unlock-file" href="#">{% trans "Unlock" %}</a></li>
|
||||
|
@ -7,7 +7,7 @@ from seahub.views import *
|
||||
from seahub.views.file import view_repo_file, view_history_file, view_trash_file,\
|
||||
view_snapshot_file, file_edit, view_shared_file, view_file_via_shared_dir,\
|
||||
text_diff, view_priv_shared_file, view_raw_file, view_raw_shared_file, \
|
||||
download_file, view_lib_file
|
||||
download_file, view_lib_file, file_access
|
||||
from seahub.views.repo import repo, repo_history_view, view_shared_dir, \
|
||||
view_shared_upload_link
|
||||
from notifications.views import notification_list
|
||||
@ -70,6 +70,7 @@ urlpatterns = patterns(
|
||||
(r'^repo/upload_error/(?P<repo_id>[-0-9a-f]{36})/$', upload_file_error),
|
||||
(r'^repo/update_error/(?P<repo_id>[-0-9a-f]{36})/$', update_file_error),
|
||||
url(r'^repo/file_revisions/(?P<repo_id>[-0-9a-f]{36})/$', file_revisions, name='file_revisions'),
|
||||
url(r'^repo/file-access/(?P<repo_id>[-0-9a-f]{36})/$', file_access, name='file_access'),
|
||||
url(r'^repo/text_diff/(?P<repo_id>[-0-9a-f]{36})/$', text_diff, name='text_diff'),
|
||||
url(r'^repo/(?P<repo_id>[-0-9a-f]{36})/$', repo, name='repo'),
|
||||
url(r'^repo/history/(?P<repo_id>[-0-9a-f]{36})/$', repo_history, name='repo_history'),
|
||||
|
@ -641,6 +641,30 @@ if EVENTS_CONFIG_FILE:
|
||||
def get_org_user_events(org_id, username, start, count):
|
||||
return _get_events(username, start, count, org_id=org_id)
|
||||
|
||||
def generate_file_audit_event_type(e):
|
||||
return {
|
||||
'file-download-web': (_('web'), ''),
|
||||
'file-download-share-link': (_('share-link'),''),
|
||||
'file-download-api': (_('API'), e.device),
|
||||
'repo-download-sync': (_('download-sync'), e.device),
|
||||
'repo-upload-sync': (_('upload-sync'), e.device),
|
||||
}[e.etype]
|
||||
|
||||
def get_file_audit_events_by_path(email, org_id, repo_id, file_path, start, limit):
|
||||
"""Return file audit events list by file path. (If no file audit, return 'None')
|
||||
|
||||
For example:
|
||||
``get_file_audit_events_by_path(email, org_id, repo_id, file_path, 0, 10)`` returns the first 10
|
||||
events.
|
||||
``get_file_audit_events_by_path(email, org_id, repo_id, file_path, 5, 10)`` returns the 6th through
|
||||
15th events.
|
||||
"""
|
||||
with _get_seafevents_session() as session:
|
||||
events = seafevents.get_file_audit_events_by_path(session,
|
||||
email, org_id, repo_id, file_path, start, limit)
|
||||
|
||||
return events if events else None
|
||||
|
||||
def get_file_audit_events(email, org_id, repo_id, start, limit):
|
||||
"""Return file audit events list. (If no file audit, return 'None')
|
||||
|
||||
@ -701,6 +725,10 @@ else:
|
||||
pass
|
||||
def get_org_user_events():
|
||||
pass
|
||||
def generate_file_audit_event_type():
|
||||
pass
|
||||
def get_file_audit_events_by_path():
|
||||
pass
|
||||
def get_file_audit_events():
|
||||
pass
|
||||
def get_file_update_events():
|
||||
|
@ -56,8 +56,10 @@ from seahub.utils import show_delete_days, render_error, is_org_context, \
|
||||
render_permission_error, is_pro_version, \
|
||||
is_textual_file, mkstemp, EMPTY_SHA1, HtmlDiff, \
|
||||
check_filename_with_rename, gen_inner_file_get_url, normalize_file_path, \
|
||||
user_traffic_over_limit, do_md5
|
||||
user_traffic_over_limit, do_md5, get_file_audit_events_by_path, \
|
||||
generate_file_audit_event_type
|
||||
from seahub.utils.ip import get_remote_ip
|
||||
from seahub.utils.timeutils import utc_to_local
|
||||
from seahub.utils.file_types import (IMAGE, PDF, DOCUMENT, SPREADSHEET, AUDIO,
|
||||
MARKDOWN, TEXT, OPENDOCUMENT, VIDEO)
|
||||
from seahub.utils.star import is_file_starred
|
||||
@ -1568,3 +1570,81 @@ def view_priv_shared_file(request, token):
|
||||
'accessible_repos': accessible_repos,
|
||||
'save_to_link': save_to_link,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@login_required
|
||||
def file_access(request, repo_id):
|
||||
"""List file access log.
|
||||
"""
|
||||
|
||||
if not is_pro_version():
|
||||
raise Http404
|
||||
|
||||
referer = request.META.get('HTTP_REFERER', None)
|
||||
next = settings.SITE_ROOT if referer is None else referer
|
||||
|
||||
repo = get_repo(repo_id)
|
||||
if not repo:
|
||||
messages.error(request, _("Library does not exist"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
path = request.GET.get('p', None)
|
||||
if not path:
|
||||
messages.error(request, _("Argument missing"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
if not seafile_api.get_file_id_by_path(repo_id, path):
|
||||
messages.error(request, _("File does not exist"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
# perm check
|
||||
if check_folder_permission(request, repo_id, path) != 'rw':
|
||||
messages.error(request, _("Permission denied"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
# Make sure page request is an int. If not, deliver first page.
|
||||
try:
|
||||
current_page = int(request.GET.get('page', '1'))
|
||||
per_page = int(request.GET.get('per_page', '100'))
|
||||
except ValueError:
|
||||
current_page = 1
|
||||
per_page = 100
|
||||
|
||||
start = per_page * (current_page - 1)
|
||||
limit = per_page + 1
|
||||
|
||||
if is_org_context(request):
|
||||
org_id = request.user.org.org_id
|
||||
events = get_file_audit_events_by_path(None, org_id, repo_id, path, start, limit)
|
||||
else:
|
||||
events = get_file_audit_events_by_path(None, 0, repo_id, path, start, limit)
|
||||
|
||||
events = events if events else []
|
||||
if len(events) == per_page + 1:
|
||||
page_next = True
|
||||
else:
|
||||
page_next = False
|
||||
|
||||
events = events[:per_page]
|
||||
|
||||
for ev in events:
|
||||
ev.repo = get_repo(ev.repo_id)
|
||||
ev.filename = os.path.basename(ev.file_path)
|
||||
ev.time = utc_to_local(ev.timestamp)
|
||||
ev.event_type, ev.show_device = generate_file_audit_event_type(ev)
|
||||
|
||||
filename = os.path.basename(path)
|
||||
zipped = gen_path_link(path, repo.name)
|
||||
extra_href = "&p=%s" % urlquote(path)
|
||||
return render_to_response('file_access.html', {
|
||||
'repo': repo,
|
||||
'path': path,
|
||||
'filename': filename,
|
||||
'zipped': zipped,
|
||||
'events': events,
|
||||
'extra_href': extra_href,
|
||||
'current_page': current_page,
|
||||
'prev_page': current_page-1,
|
||||
'next_page': current_page+1,
|
||||
'per_page': per_page,
|
||||
'page_next': page_next,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
144
tests/seahub/views/test_file.py
Normal file
144
tests/seahub/views/test_file.py
Normal file
@ -0,0 +1,144 @@
|
||||
import datetime
|
||||
|
||||
from mock import patch
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from seahub.test_utils import BaseTestCase
|
||||
|
||||
|
||||
class FileAccessLogTest(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.login_as(self.user)
|
||||
self.file_path = self.file
|
||||
self.repo_id = self.repo.id
|
||||
|
||||
def tearDown(self):
|
||||
self.remove_repo()
|
||||
|
||||
def generate_file_audit_event_type(self, e):
|
||||
return {
|
||||
'file-download-web': ('web', ''),
|
||||
'file-download-share-link': ('share-link',''),
|
||||
'file-download-api': ('API', e.device),
|
||||
'repo-download-sync': ('download-sync', e.device),
|
||||
'repo-upload-sync': ('upload-sync', e.device),
|
||||
}[e.etype]
|
||||
|
||||
@patch('seahub.views.file.is_pro_version')
|
||||
def test_can_not_render_if_not_pro(self, mock_is_pro_version):
|
||||
mock_is_pro_version.return_value = False
|
||||
|
||||
url = reverse('file_access', args=[self.repo_id]) + '?p=' + self.file_path
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
||||
@patch('seahub.views.file.generate_file_audit_event_type')
|
||||
@patch('seahub.views.file.get_file_audit_events_by_path')
|
||||
@patch('seahub.views.file.is_pro_version')
|
||||
def test_can_show_web_type(self, mock_is_pro_version,
|
||||
mock_get_file_audit_events_by_path, mock_generate_file_audit_event_type):
|
||||
|
||||
etype = 'file-download-web'
|
||||
event = Event(self.user.email, self.repo_id, self.file_path, etype)
|
||||
|
||||
mock_is_pro_version.return_value = True
|
||||
mock_get_file_audit_events_by_path.return_value = [event]
|
||||
mock_generate_file_audit_event_type.side_effect = self.generate_file_audit_event_type
|
||||
|
||||
url = reverse('file_access', args=[self.repo_id]) + '?p=' + self.file_path
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertTemplateUsed(resp, 'file_access.html')
|
||||
self.assertContains(resp, 'web')
|
||||
|
||||
@patch('seahub.views.file.generate_file_audit_event_type')
|
||||
@patch('seahub.views.file.get_file_audit_events_by_path')
|
||||
@patch('seahub.views.file.is_pro_version')
|
||||
def test_can_show_share_link_type(self, mock_is_pro_version,
|
||||
mock_get_file_audit_events_by_path, mock_generate_file_audit_event_type):
|
||||
|
||||
etype = 'file-download-share-link'
|
||||
event = Event(self.user.email, self.repo_id, self.file_path, etype)
|
||||
|
||||
mock_is_pro_version.return_value = True
|
||||
mock_get_file_audit_events_by_path.return_value = [event]
|
||||
mock_generate_file_audit_event_type.side_effect = self.generate_file_audit_event_type
|
||||
|
||||
url = reverse('file_access', args=[self.repo_id]) + '?p=' + self.file_path
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertTemplateUsed(resp, 'file_access.html')
|
||||
self.assertContains(resp, 'share-link')
|
||||
|
||||
@patch('seahub.views.file.generate_file_audit_event_type')
|
||||
@patch('seahub.views.file.get_file_audit_events_by_path')
|
||||
@patch('seahub.views.file.is_pro_version')
|
||||
def test_can_show_api_type(self, mock_is_pro_version,
|
||||
mock_get_file_audit_events_by_path, mock_generate_file_audit_event_type):
|
||||
|
||||
etype = 'file-download-api'
|
||||
event = Event(self.user.email, self.repo_id, self.file_path, etype)
|
||||
|
||||
mock_is_pro_version.return_value = True
|
||||
mock_get_file_audit_events_by_path.return_value = [event]
|
||||
mock_generate_file_audit_event_type.side_effect = self.generate_file_audit_event_type
|
||||
|
||||
url = reverse('file_access', args=[self.repo_id]) + '?p=' + self.file_path
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertTemplateUsed(resp, 'file_access.html')
|
||||
self.assertContains(resp, 'API')
|
||||
|
||||
@patch('seahub.views.file.generate_file_audit_event_type')
|
||||
@patch('seahub.views.file.get_file_audit_events_by_path')
|
||||
@patch('seahub.views.file.is_pro_version')
|
||||
def test_can_show_download_sync_type(self, mock_is_pro_version,
|
||||
mock_get_file_audit_events_by_path, mock_generate_file_audit_event_type):
|
||||
|
||||
etype = 'repo-download-sync'
|
||||
event = Event(self.user.email, self.repo_id, self.file_path, etype)
|
||||
|
||||
mock_is_pro_version.return_value = True
|
||||
mock_get_file_audit_events_by_path.return_value = [event]
|
||||
mock_generate_file_audit_event_type.side_effect = self.generate_file_audit_event_type
|
||||
|
||||
url = reverse('file_access', args=[self.repo_id]) + '?p=' + self.file_path
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertTemplateUsed(resp, 'file_access.html')
|
||||
self.assertContains(resp, 'download-sync')
|
||||
|
||||
@patch('seahub.views.file.generate_file_audit_event_type')
|
||||
@patch('seahub.views.file.get_file_audit_events_by_path')
|
||||
@patch('seahub.views.file.is_pro_version')
|
||||
def test_can_show_upload_sync_type(self, mock_is_pro_version,
|
||||
mock_get_file_audit_events_by_path, mock_generate_file_audit_event_type):
|
||||
|
||||
etype = 'repo-upload-sync'
|
||||
event = Event(self.user.email, self.repo_id, self.file_path, etype)
|
||||
|
||||
mock_is_pro_version.return_value = True
|
||||
mock_get_file_audit_events_by_path.return_value = [event]
|
||||
mock_generate_file_audit_event_type.side_effect = self.generate_file_audit_event_type
|
||||
|
||||
url = reverse('file_access', args=[self.repo_id]) + '?p=' + self.file_path
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertTemplateUsed(resp, 'file_access.html')
|
||||
self.assertContains(resp, 'upload-sync')
|
||||
|
||||
|
||||
class Event(object):
|
||||
|
||||
def __init__(self, user, repo_id, file_path, etype):
|
||||
|
||||
self.device = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36'
|
||||
self.ip = '192.168.1.124'
|
||||
self.org_id = -1
|
||||
self.timestamp = datetime.datetime.now()
|
||||
self.user = user
|
||||
self.repo_id = repo_id
|
||||
self.file_path = file_path
|
||||
self.etype = etype
|
Loading…
Reference in New Issue
Block a user