1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-01 23:20:51 +00:00

add admin operation logs page

This commit is contained in:
lian
2017-03-09 17:42:59 +08:00
parent 893f3db8c0
commit dd88c4e043
23 changed files with 699 additions and 17 deletions

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import datetime
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='AdminLog',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('email', models.EmailField(max_length=254, db_index=True)),
('operation', models.CharField(max_length=255, db_index=True)),
('detail', models.TextField()),
('datetime', models.DateTimeField(default=datetime.datetime.now)),
],
options={
'ordering': ['-datetime'],
},
),
]

View File

View File

@@ -0,0 +1,74 @@
# Copyright (c) 2012-2017 Seafile Ltd.
import json
import datetime
from django.db import models
from django.db.models import Q
from django.dispatch import receiver
from seahub.admin_log.signals import admin_operation
ADMIN_LOG_OPERATION_TYPE = ('repo_transfer', 'repo_delete',
'group_create', 'group_transfer', 'group_delete',
'user_add', 'user_delete')
## operation: detail
# 'repo_transfer': {'id': repo_id, 'name': repo_name, 'from': from_user, 'to': to_user}
# 'repo_delete': {'id': repo_id, 'name': repo_name, 'owner': repo_owner}
# 'group_create': {'id': group_id, 'name': group_name, 'owner': group_owner}
# 'group_transfer': {'id': group_id, 'name': group_name, 'from': from_user, 'to': to_user}
# 'group_delete': {'id': group_id, 'name': group_name, 'owner': group_owner}
# 'user_add': {'email': new_user}
# 'user_delete': {'email': deleted_user}
class AdminLogManager(models.Manager):
def add_admin_log(self, email, operation, detail):
model= super(AdminLogManager, self).create(
email=email, operation=operation, detail=detail)
model.save()
return model
def get_admin_logs(self, email=None, operation=None):
logs = super(AdminLogManager, self).all()
if email and operation:
filtered_logs = logs.filter(Q(email=email) & Q(operation = operation))
elif email:
filtered_logs = logs.filter(email=email)
elif operation:
filtered_logs = logs.filter(operation=operation)
else:
filtered_logs = logs
return filtered_logs
class AdminLog(models.Model):
email = models.EmailField(db_index=True)
operation = models.CharField(max_length=255, db_index=True)
detail = models.CharField(max_length=255)
datetime = models.DateTimeField(default=datetime.datetime.now)
objects = AdminLogManager()
class Meta:
ordering = ["-datetime"]
###### signal handlers
@receiver(admin_operation)
def admin_operation_cb(sender, **kwargs):
admin_name = kwargs['admin_name']
operation = kwargs['operation']
detail = kwargs['detail']
detail_json = json.dumps(detail)
AdminLog.objects.add_admin_log(admin_name,
operation, detail_json)

View File

@@ -0,0 +1,4 @@
# Copyright (c) 2012-2017 Seafile Ltd.
import django.dispatch
admin_operation = django.dispatch.Signal(providing_args=["admin_name", "operation", "detail"])

View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -16,7 +16,7 @@ from seahub.utils import is_valid_username
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
from seahub.group.utils import is_group_member, is_group_admin, \
validate_group_name, check_group_name_conflict
from seahub.admin_log.signals import admin_operation
from seahub.api2.utils import api_error
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.authentication import TokenAuthentication
@@ -129,15 +129,25 @@ class AdminGroups(APIView):
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
username = request.user.username
new_owner = group_owner or username
# create group.
try:
group_id = ccnet_api.create_group(group_name, group_owner or username)
group_id = ccnet_api.create_group(group_name, new_owner)
except SearpcError as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
# send admin operation log signal
admin_op_detail = {
"id": group_id,
"name": group_name,
"owner": new_owner,
}
admin_operation.send(sender=None, admin_name=username,
operation='group_create', detail=admin_op_detail)
# get info of new group
group_info = get_group_info(group_id)
@@ -198,16 +208,32 @@ class AdminGroup(APIView):
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
group_info = get_group_info(group_id)
# send admin operation log signal
admin_op_detail = {
"id": group_id,
"name": group.group_name,
"from": old_owner,
"to": new_owner,
}
admin_operation.send(sender=None, admin_name=request.user.username,
operation='group_transfer', detail=admin_op_detail)
group_info = get_group_info(group_id)
return Response(group_info)
def delete(self, request, group_id):
""" Dismiss a specific group
"""
group_id = int(group_id)
group = ccnet_api.get_group(group_id)
if not group:
return Response({'success': True})
group_owner = group.creator_name
group_name = group.group_name
try:
group_id = int(group_id)
ccnet_api.remove_group(group_id)
seafile_api.remove_group_repos(group_id)
except Exception as e:
@@ -215,4 +241,13 @@ class AdminGroup(APIView):
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
# send admin operation log signal
admin_op_detail = {
"id": group_id,
"name": group_name,
"owner": group_owner,
}
admin_operation.send(sender=None, admin_name=request.user.username,
operation='group_delete', detail=admin_op_detail)
return Response({'success': True})

View File

@@ -17,6 +17,7 @@ from seahub.api2.utils import api_error
from seahub.base.accounts import User
from seahub.signals import repo_deleted
from seahub.views import get_system_default_repo_id
from seahub.admin_log.signals import admin_operation
try:
from seahub.settings import MULTI_TENANCY
@@ -150,24 +151,33 @@ class AdminLibrary(APIView):
return api_error(status.HTTP_404_NOT_FOUND,
'Library %s not found.' % repo_id)
repo_name = repo.name
repo_owner = seafile_api.get_repo_owner(repo_id)
if not repo_owner:
repo_owner = seafile_api.get_org_repo_owner(repo_id)
related_usernames = seaserv.get_related_users_by_repo(repo_id)
try:
seafile_api.remove_repo(repo_id)
repo_deleted.send(sender=None,
org_id=-1,
usernames=related_usernames,
repo_owner=repo_owner,
repo_id=repo_id,
repo_name=repo.name)
related_usernames = seaserv.get_related_users_by_repo(repo_id)
# send signal for seafevents
repo_deleted.send(sender=None, org_id=-1, usernames=related_usernames,
repo_owner=repo_owner, repo_id=repo_id, repo_name=repo.name)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
# send admin operation log signal
admin_op_detail = {
"id": repo_id,
"name": repo_name,
"owner": repo_owner,
}
admin_operation.send(sender=None, admin_name=request.user.username,
operation='repo_delete', detail=admin_op_detail)
return Response({'success': True})
def put(self, request, repo_id, format=None):
@@ -257,6 +267,16 @@ class AdminLibrary(APIView):
break
# send admin operation log signal
admin_op_detail = {
"id": repo_id,
"name": repo.name,
"from": repo_owner,
"to": new_owner,
}
admin_operation.send(sender=None, admin_name=request.user.username,
operation='repo_transfer', detail=admin_op_detail)
repo = seafile_api.get_repo(repo_id)
repo_info = get_repo_info(repo)

View File

@@ -0,0 +1,89 @@
import json
import logging
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from django.core.urlresolvers import reverse
from seahub.utils.timeutils import datetime_to_isoformat_timestr
from seahub.admin_log.models import AdminLog, ADMIN_LOG_OPERATION_TYPE
from seahub.api2.permissions import IsProVersion
from seahub.api2.utils import api_error
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.endpoints.admin.utils import generate_links_header_for_paginator
logger = logging.getLogger(__name__)
def get_log_info(log_obj):
isoformat_timestr = datetime_to_isoformat_timestr(log_obj.datetime)
log_info = {
"email": log_obj.email,
"operation": log_obj.operation,
"detail": json.loads(log_obj.detail),
"datetime": isoformat_timestr,
}
return log_info
class AdminLogs(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
permission_classes = (IsAdminUser, IsProVersion)
def get(self, request):
""" List all logs
Permission checking:
1. Admin user;
"""
email = request.GET.get('email', '')
operation = request.GET.get('operation', '')
if operation:
if operation not in ADMIN_LOG_OPERATION_TYPE:
error_msg = 'operation invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
page = int(request.GET.get('page', '1'))
per_page = int(request.GET.get('per_page', '100'))
except ValueError:
page = 1
per_page = 100
if page <= 0:
error_msg = 'page invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if per_page <= 0:
error_msg = 'per_page invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# generate data result
data = []
offset = per_page * (page -1)
total_count = AdminLog.objects.get_admin_logs(email=email, operation=operation).count()
admin_logs = AdminLog.objects.get_admin_logs(email=email, operation=operation)[offset:offset+per_page]
for log in admin_logs:
log_info = get_log_info(log)
data.append(log_info)
result = {'data': data, 'total_count': total_count}
resp = Response(result)
## generate `Links` header for paginator
options_dict = {'email': email, 'operation': operation}
base_url = reverse('api-v2.1-admin-admin-logs')
links_header = generate_links_header_for_paginator(base_url,
page, per_page, total_count, options_dict)
resp['Links'] = links_header
return resp

View File

@@ -2,6 +2,7 @@
import re
import datetime
import time
import urllib
from seahub.utils import get_log_events_by_time
@@ -27,3 +28,50 @@ def get_log_events_by_type_and_time(log_type, start, end):
events = get_log_events_by_time(log_type, start_timestamp, end_timestamp)
events = events if events else []
return events
def is_first_page(page):
return True if page == 1 else False
def is_last_page(page, per_page, total_count):
if page * per_page >= total_count:
return True
else:
return False
def generate_links_header_for_paginator(base_url, page, per_page, total_count, option_dict={}):
if type(option_dict) is not dict:
return ''
query_dict = {'page': 1, 'per_page': per_page}
query_dict.update(option_dict)
# generate first page url
first_page_url = base_url + '?' + urllib.urlencode(query_dict)
# generate last page url
last_page_query_dict = {'page': (total_count / per_page) + 1}
query_dict.update(last_page_query_dict)
last_page_url = base_url + '?' + urllib.urlencode(query_dict)
# generate next page url
next_page_query_dict = {'page': page + 1}
query_dict.update(next_page_query_dict)
next_page_url = base_url + '?' + urllib.urlencode(query_dict)
# generate prev page url
prev_page_query_dict = {'page': page - 1}
query_dict.update(prev_page_query_dict)
prev_page_url = base_url + '?' + urllib.urlencode(query_dict)
# generate `Links` header
links_header = ''
if is_first_page(page):
links_header = '<%s>; rel="next", <%s>; rel="last"' % (next_page_url, last_page_url)
elif is_last_page(page, per_page, total_count):
links_header = '<%s>; rel="first", <%s>; rel="prev"' % (first_page_url, prev_page_url)
else:
links_header = '<%s>; rel="next", <%s>; rel="last", <%s>; rel="first", <%s>; rel="prev"' % \
(next_page_url, last_page_url, first_page_url, prev_page_url)
return links_header

View File

@@ -9,6 +9,8 @@ from django.conf import settings
from seaserv import check_permission, is_repo_owner, ccnet_api
from seahub.utils import is_pro_version
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
class IsRepoWritable(BasePermission):
@@ -76,3 +78,11 @@ class CanGenerateUploadLink(BasePermission):
"""
def has_permission(self, request, *args, **kwargs):
return request.user.permissions.can_generate_upload_link()
class IsProVersion(BasePermission):
"""
Check whether Seafile is pro version
"""
def has_permission(self, request, *args, **kwargs):
return is_pro_version()

View File

@@ -214,6 +214,7 @@ INSTALLED_APPS = (
'seahub.help',
'seahub.thumbnail',
'seahub.password_session',
'seahub.admin_log',
)
# Enabled or disable constance(web settings).

View File

@@ -67,6 +67,10 @@
<a href="{{ SITE_ROOT }}sys/termsadmin/"><span class="sf2-icon-wiki"></span>{% trans "Terms and Conditions" %}</a>
</li>
{% endif %}
<li class="tab<% if (cur_tab == 'admin-logs') { %> tab-cur<% } %>">
<a href="{{ SITE_ROOT }}sysadmin/#admin-logs/"><span class="sf2-icon-clock"></span>{% trans "Admin Logs" %}</a>
</li>
</ul>
<% if (cur_tab == 'libraries') { %>
<% if (option == 'all') { %>
@@ -839,3 +843,75 @@
<% } %>
</td>
</script>
<script type="text/template" id="admin-logs-tmpl">
<div class="hd ovhd">
<h3 class="fleft">{% trans "Admin Logs" %}</h3>
</div>
<span class="loading-icon loading-tip"></span>
<table>
<thead>
<tr>
<th width="20%">{% trans "Email" %}</th>
<th width="15%">{% trans "Operation" %}</th>
<th width="50%">{% trans "Detail" %}</th>
<th width="15%">{% trans "Time" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="empty-tips hide">
<h2 class="alc">{% trans "No Admin Logs" %}</h2>
</div>
<div id="paginator">
<a class="prev js-previous hide" href="#">{% trans "Previous" %}</a>
<a class="next js-next hide" href="#">{% trans "Next" %}</a>
</div>
</script>
<script type="text/template" id="admin-log-item-tmpl">
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(email)); %>/"><%- email %></a></td>
<% if (operation=='repo_delete') { %>
<td>{% trans "Delete Library" %}</td>
<td>{% trans "Delete library" %} <strong title="<%- detail['id'] %>"><%- detail['name'] %></strong></td>
<% } %>
<% if (operation=='repo_transfer') { %>
<td>{% trans "Transfer Library" %}</td>
<% if (app.pageOptions.is_pro && app.pageOptions.enable_sys_admin_view_repo) { %>
<td>{% trans "Transfer library" %} <a href="#libs/<%- detail['id'] %>/"><%- detail['name'] %></a> {% trans "from" %} <a href="{{ SITE_ROOT }}useradmin/info/<%- detail['from'] %>/"><%- detail['from'] %></a> {% trans "to" %} <a href="{{ SITE_ROOT }}useradmin/info/<%- detail['to'] %>/"><%- detail['to'] %></a></td>
<% } else { %>
<td>{% trans "Transfer library" %} <%- detail['name'] %> {% trans "from" %} <a href="{{ SITE_ROOT }}useradmin/info/<%- detail['from'] %>/"><%- detail['from'] %></a> {% trans "to" %} <a href="{{ SITE_ROOT }}useradmin/info/<%- detail['to'] %>/"><%- detail['to'] %></a></td>
<% } %>
<% } %>
<% if (operation=='group_create') { %>
<td>{% trans "Create Group" %}</td>
<td>{% trans "Create group" %} <span><a href="#groups/<%- detail['id'] %>/"><%- detail['name'] %></span></td>
<% } %>
<% if (operation=='group_transfer') { %>
<td>{% trans "Transfer Group" %}</td>
<td>{% trans "Transfer group" %} <a href="#groups/<%- detail['id'] %>/"><%- detail['name'] %></a> {% trans "from" %} <a href="{{ SITE_ROOT }}useradmin/info/<%- detail['from'] %>/"><%- detail['from'] %></a> {% trans "to" %} <a href="{{ SITE_ROOT }}useradmin/info/<%- detail['to'] %>/"><%- detail['to'] %></a></td>
<% } %>
<% if (operation=='group_delete') { %>
<td>{% trans "Delete Group" %}</td>
<td>{% trans "Delete group" %} <strong title="<%- detail['id'] %>"><%- detail['name'] %></strong></td>
<% } %>
<% if (operation=='user_add') { %>
<td>{% trans "Add User" %}</td>
<td>{% trans "Add user" %} <span><a href="{{ SITE_ROOT }}useradmin/info/<%- detail['email'] %>/"><%- detail['email'] %></a></span></td>
<% } %>
<% if (operation=='user_delete') { %>
<td>{% trans "Delete User" %}</td>
<td>{% trans "Delete user" %} <strong><%- detail['email'] %></strong></td>
<% } %>
<td><time title="<%- time %>"><%- time_from_now %></time></td>
</script>

View File

@@ -73,6 +73,9 @@
<a href="{{ SITE_ROOT }}sys/termsadmin/"><span class="sf2-icon-wiki"></span>{% trans "Terms and Conditions" %}</a>
</li>
{% endif %}
<li class="tab">
<a href="{{ SITE_ROOT }}sysadmin/#admin-logs/"><span class="sf2-icon-clock"></span>{% trans "Admin Logs" %}</a>
</li>
</ul>
{% endblock %}
</div>

View File

@@ -55,6 +55,7 @@ from seahub.api2.endpoints.admin.group_libraries import AdminGroupLibraries, Adm
from seahub.api2.endpoints.admin.group_members import AdminGroupMembers, AdminGroupMember
from seahub.api2.endpoints.admin.shares import AdminShares
from seahub.api2.endpoints.admin.users_batch import AdminUsersBatch
from seahub.api2.endpoints.admin.logs import AdminLogs
# Uncomment the next two lines to enable the admin:
#from django.contrib import admin
@@ -213,6 +214,8 @@ urlpatterns = patterns(
url(r'^api/v2.1/admin/trash-libraries/$', AdminTrashLibraries.as_view(), name='api-v2.1-admin-trash-libraries'),
url(r'^api/v2.1/admin/trash-libraries/(?P<repo_id>[-0-9a-f]{36})/$', AdminTrashLibrary.as_view(), name='api-v2.1-admin-trash-library'),
url(r'^api/v2.1/admin/shares/$', AdminShares.as_view(), name='api-v2.1-admin-shares'),
# TODO
url(r'^api/v2.1/admin/admin-logs/$', AdminLogs.as_view(), name='api-v2.1-admin-admin-logs'),
url(r'^api/v2.1/admin/users/batch/$', AdminUsersBatch.as_view(), name='api-v2.1-admin-users-batch'),

View File

@@ -54,11 +54,11 @@ from seahub.utils.user_permissions import (get_basic_user_roles,
from seahub.views import get_system_default_repo_id
from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm, \
TermsAndConditionsForm
from seahub.profile.forms import ProfileForm, DetailedProfileForm
from seahub.options.models import UserOptions
from seahub.profile.models import Profile, DetailedProfile
from seahub.signals import repo_deleted
from seahub.share.models import FileShare, UploadLinkShare
from seahub.admin_log.signals import admin_operation
import seahub.settings as settings
from seahub.settings import INIT_PASSWD, SITE_NAME, SITE_ROOT, \
SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, SEND_EMAIL_ON_RESETTING_USER_PASSWD, \
@@ -712,6 +712,13 @@ def user_remove(request, email):
except User.DoesNotExist:
messages.error(request, _(u'Failed to delete: the user does not exist'))
# send admin operation log signal
admin_op_detail = {
"email": email,
}
admin_operation.send(sender=None, admin_name=request.user.username,
operation='user_delete', detail=admin_op_detail)
return HttpResponseRedirect(next)
@login_required
@@ -985,6 +992,13 @@ def user_add(request):
err_msg = _(u'Fail to add user %s.') % email
return HttpResponse(json.dumps({'error': err_msg}), status=403, content_type=content_type)
# send admin operation log signal
admin_op_detail = {
"email": email,
}
admin_operation.send(sender=None, admin_name=request.user.username,
operation='user_add', detail=admin_op_detail)
if user:
User.objects.update_role(email, role)
if config.FORCE_PASSWORD_CHANGE:
@@ -1832,7 +1846,6 @@ def batch_add_user(request):
Profile.objects.add_or_update(username, nickname, '')
except Exception as e:
logger.error(e)
continue
try:
department = row[3].strip() or ''
@@ -1840,7 +1853,6 @@ def batch_add_user(request):
DetailedProfile.objects.add_or_update(username, department, '')
except Exception as e:
logger.error(e)
continue
try:
role = row[4].strip() or ''
@@ -1848,7 +1860,6 @@ def batch_add_user(request):
User.objects.update_role(username, role)
except Exception as e:
logger.error(e)
continue
try:
space_quota_mb = row[5].strip() or ''
@@ -1858,7 +1869,6 @@ def batch_add_user(request):
seafile_api.set_user_quota(username, space_quota)
except Exception as e:
logger.error(e)
continue
send_html_email_with_dj_template(
username, dj_template='sysadmin/user_batch_add_email.html',
@@ -1869,6 +1879,13 @@ def batch_add_user(request):
'password': password,
})
# send admin operation log signal
admin_op_detail = {
"email": username,
}
admin_operation.send(sender=None, admin_name=request.user.username,
operation='user_add', detail=admin_op_detail)
messages.success(request, _('Import succeeded'))
else:
messages.error(request, _(u'Please select a csv file first.'))

View File

@@ -179,6 +179,8 @@ define([
case 'admin-trash-library': return siteRoot + 'api/v2.1/admin/trash-libraries/' + options.repo_id + '/';
case 'admin_shares': return siteRoot + 'api/v2.1/admin/shares/';
case 'sys_group_admin_export_excel': return siteRoot + 'sys/groupadmin/export-excel/';
case 'admin-logs': return siteRoot + 'api/v2.1/admin/admin-logs/';
}
},

View File

@@ -0,0 +1,40 @@
define([
'underscore',
'backbone.paginator',
'common',
'sysadmin-app/models/admin-log'
], function(_, BackbonePaginator, Common, AdminLogModel) {
'use strict';
var AdminLogCollection = Backbone.PageableCollection.extend({
model: AdminLogModel,
url: function() {
return Common.getUrl({name: 'admin-logs'});
},
state: {
firstPage: 1,
pageSize: 100,
},
// Setting `null` as the value of any mapping
// will remove it from the query string.
queryParams: {
currentPage: "page",
pageSize: "per_page",
totalPages: null,
totalRecords: null,
},
parseState: function (resp) {
return {totalRecords: resp.total_count};
},
parseRecords: function (resp) {
return resp.data;
}
});
return AdminLogCollection;
});

View File

@@ -0,0 +1,11 @@
define([
'underscore',
'backbone',
'common',
], function(_, Backbone, Common) {
'use strict';
var AdminLogModel = Backbone.Model.extend({});
return AdminLogModel;
});

View File

@@ -18,12 +18,13 @@ define([
'sysadmin-app/views/search-groups',
'sysadmin-app/views/group-repos',
'sysadmin-app/views/group-members',
'sysadmin-app/views/admin-logs',
'app/views/account'
], function($, Backbone, Common, SideNavView, DashboardView,
DesktopDevicesView, MobileDevicesView, DeviceErrorsView,
ReposView, SearchReposView, SystemReposView, TrashReposView,
SearchTrashReposView, DirView, GroupsView, SearchGroupsView,
GroupReposView, GroupMembersView, AccountView) {
GroupReposView, GroupMembersView, AdminLogsView, AccountView) {
"use strict";
@@ -42,8 +43,10 @@ define([
'libs/:repo_id(/*path)': 'showLibraryDir',
'groups/': 'showGroups',
'search-groups/': 'showSearchGroups',
'groups/:group_id/': 'showGroupLibraries',
'groups/:group_id/libs/': 'showGroupLibraries',
'groups/:group_id/members/': 'showGroupMembers',
'admin-logs/': 'showAdminLogs',
// Default
'*actions': 'showDashboard'
},
@@ -76,6 +79,8 @@ define([
this.groupReposView = new GroupReposView();
this.groupMembersView = new GroupMembersView();
this.adminLogsView = new AdminLogsView();
app.ui.accountView = this.accountView = new AccountView();
this.currentView = this.dashboardView;
@@ -223,6 +228,20 @@ define([
this.switchCurrentView(this.groupMembersView);
this.sideNavView.setCurTab('groups');
this.groupMembersView.show(group_id);
},
showAdminLogs: function() {
var url = window.location.href;
var page = url.match(/.*?page=(\d+)/);
if (page) {
var current_page = page[1];
} else {
var current_page = null;
}
this.switchCurrentView(this.adminLogsView);
this.sideNavView.setCurTab('admin-logs');
this.adminLogsView.show({'current_page': current_page});
}
});

View File

@@ -0,0 +1,38 @@
define([
'jquery',
'underscore',
'backbone',
'common',
'moment',
'app/views/widgets/hl-item-view'
], function($, _, Backbone, Common, Moment, HLItemView) {
'use strict';
var AdminLogView = HLItemView.extend({
tagName: 'tr',
template: _.template($('#admin-log-item-tmpl').html()),
events: {
},
initialize: function() {
HLItemView.prototype.initialize.call(this);
},
render: function() {
var data = this.model.toJSON(),
created_at = Moment(data['datetime']);
data['time'] = created_at.format('LLLL');
data['time_from_now'] = Common.getRelativeTimeStr(created_at);
this.$el.html(this.template(data));
return this;
}
});
return AdminLogView;
});

View File

@@ -0,0 +1,159 @@
define([
'jquery',
'underscore',
'backbone',
'common',
'sysadmin-app/views/admin-log',
'sysadmin-app/collection/admin-logs'
], function($, _, Backbone, Common, AdminLogView, AdminLogCollection) {
'use strict';
var AdminLogsView = Backbone.View.extend({
id: 'admin-logs',
template: _.template($("#admin-logs-tmpl").html()),
initialize: function() {
this.adminLogCollection = new AdminLogCollection();
this.listenTo(this.adminLogCollection, 'add', this.addOne);
this.listenTo(this.adminLogCollection, 'reset', this.reset);
this.render();
},
render: function() {
this.$el.append(this.template());
this.$table = this.$('table');
this.$tableBody = $('tbody', this.$table);
this.$loadingTip = this.$('.loading-tip');
this.$emptyTip = this.$('.empty-tips');
this.$jsPrevious = this.$('.js-previous');
this.$jsNext = this.$('.js-next');
},
events: {
'click #paginator .js-next': 'getNextPage',
'click #paginator .js-previous': 'getPreviousPage'
},
getNextPage: function() {
this.initPage();
this.adminLogCollection.getNextPage({reset: true});
return false;
},
getPreviousPage: function() {
this.initPage();
this.adminLogCollection.getPreviousPage({reset: true});
return false;
},
initPage: function() {
this.$loadingTip.show();
this.$table.hide();
this.$tableBody.empty();
this.$emptyTip.hide();
this.$jsNext.hide();
this.$jsPrevious.hide();
},
hide: function() {
this.$el.detach();
this.attached = false;
},
show: function(option) {
this.option = option;
if (!this.attached) {
this.attached = true;
$("#right-panel").html(this.$el);
}
this.getContent();
},
renderPaginator: function() {
if (this.adminLogCollection.hasNextPage()) {
this.$jsNext.show();
} else {
this.$jsNext.hide();
}
if (this.adminLogCollection.hasPreviousPage()) {
this.$jsPrevious.show();
} else {
this.$jsPrevious.hide();
}
},
getContent: function() {
this.initPage();
var _this = this;
var current_page = parseInt(this.option.current_page) || 1;
var first_page = parseInt(this.adminLogCollection.state.firstPage);
var total_page = parseInt(this.adminLogCollection.state.totalPages);
// `currentPage` must be firstPage <= currentPage < totalPages if 1-based.
if (first_page <= current_page && current_page < total_page) {
this.adminLogCollection.getPage(current_page).done(function () {
_this.$loadingTip.hide();
var current_page = _this.adminLogCollection.state.currentPage;
app.router.navigate('admin-logs/?page=' + current_page);
if (_this.adminLogCollection.length > 0) {
_this.$table.show();
} else {
_this.$emptyTip.show();
}
_this.renderPaginator();
});
} else {
// always get the first page if not use `getPage` method
_this.adminLogCollection.state.currentPage = 1;
this.adminLogCollection.fetch({
cache: false,
reset: true,
error: function(collection, response, opts) {
var err_msg;
if (response.responseText) {
if (response['status'] == 401 || response['status'] == 403) {
err_msg = gettext("Permission error");
} else {
err_msg = $.parseJSON(response.responseText).error_msg;
}
} else {
err_msg = gettext("Failed. Please check the network.");
}
Common.feedback(err_msg, 'error');
}
});
}
},
addOne: function(log) {
var view = new AdminLogView({model: log});
this.$tableBody.append(view.render().el);
},
reset: function() {
this.$loadingTip.hide();
var current_page = this.adminLogCollection.state.currentPage;
app.router.navigate('admin-logs/?page=' + current_page);
if (this.adminLogCollection.length > 0) {
this.adminLogCollection.each(this.addOne, this);
this.$table.show();
} else {
this.$emptyTip.show();
}
this.renderPaginator();
}
});
return AdminLogsView;
});