1
0
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:
lian 2015-11-27 10:55:56 +08:00
parent bda3595d6f
commit 643d036942
7 changed files with 356 additions and 2 deletions

View File

@ -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;
}

View 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 %}

View File

@ -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>

View File

@ -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'),

View File

@ -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():

View File

@ -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))

View 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