audit-logs: allow showing individual entries for audit log.
This commit is contained in:
parent
cb071ab951
commit
b95e835ebb
8 changed files with 159 additions and 5 deletions
|
|
@ -33,6 +33,10 @@ def admin_routes(config):
|
|||
name='admin_audit_logs',
|
||||
pattern='/audit_logs')
|
||||
|
||||
config.add_route(
|
||||
name='admin_audit_log_entry',
|
||||
pattern='/audit_logs/{audit_log_id}')
|
||||
|
||||
config.add_route(
|
||||
name='pull_requests_global_0', # backward compat
|
||||
pattern='/pull_requests/{pull_request_id:\d+}')
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
import logging
|
||||
|
||||
from pyramid.httpexceptions import HTTPNotFound
|
||||
from pyramid.view import view_config
|
||||
|
||||
from rhodecode.apps._base import BaseAppView
|
||||
|
|
@ -70,3 +71,21 @@ class AdminAuditLogsView(BaseAppView):
|
|||
c.audit_logs = Page(users_log, page=p, items_per_page=10,
|
||||
url=url_generator)
|
||||
return self._get_template_context(c)
|
||||
|
||||
@LoginRequired()
|
||||
@HasPermissionAllDecorator('hg.admin')
|
||||
@view_config(
|
||||
route_name='admin_audit_log_entry', request_method='GET',
|
||||
renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
|
||||
def admin_audit_log_entry(self):
|
||||
c = self.load_default_context()
|
||||
audit_log_id = self.request.matchdict['audit_log_id']
|
||||
|
||||
c.audit_log_entry = UserLog.query()\
|
||||
.options(joinedload(UserLog.user))\
|
||||
.options(joinedload(UserLog.repository))\
|
||||
.filter(UserLog.user_log_id == audit_log_id).scalar()
|
||||
if not c.audit_log_entry:
|
||||
raise HTTPNotFound()
|
||||
|
||||
return self._get_template_context(c)
|
||||
|
|
|
|||
|
|
@ -133,9 +133,6 @@ def _store_log(action_name, action_data, user_id, username, user_data,
|
|||
|
||||
user_log.action_date = datetime.datetime.now()
|
||||
|
||||
log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
|
||||
action_name, user_id, username, ip_address)
|
||||
|
||||
return user_log
|
||||
|
||||
|
||||
|
|
@ -239,19 +236,27 @@ def store(action, user, action_data=None, user_data=None, ip_addr=None,
|
|||
repository_id = getattr(
|
||||
Repository.get_by_repo_name(repository_name), 'repo_id', None)
|
||||
|
||||
action_name = safe_unicode(action)
|
||||
ip_address = safe_unicode(ip_addr)
|
||||
|
||||
user_log = _store_log(
|
||||
action_name=safe_unicode(action),
|
||||
action_name=action_name,
|
||||
action_data=action_data or {},
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
user_data=user_data or {},
|
||||
ip_address=safe_unicode(ip_addr),
|
||||
ip_address=ip_address,
|
||||
repository_id=repository_id,
|
||||
repository_name=repository_name
|
||||
)
|
||||
|
||||
sa_session.add(user_log)
|
||||
if commit:
|
||||
sa_session.commit()
|
||||
|
||||
entry_id = user_log.entry_id or ''
|
||||
log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
|
||||
entry_id, action_name, user_id, username, ip_address)
|
||||
|
||||
except Exception:
|
||||
log.exception('AUDIT: failed to store audit log')
|
||||
|
|
|
|||
|
|
@ -1252,6 +1252,10 @@ class UserLog(Base, BaseModel):
|
|||
'action': self.action,
|
||||
}
|
||||
|
||||
@hybrid_property
|
||||
def entry_id(self):
|
||||
return self.user_log_id
|
||||
|
||||
@property
|
||||
def action_as_day(self):
|
||||
return datetime.date(*self.action_date.timetuple()[:3])
|
||||
|
|
|
|||
|
|
@ -250,6 +250,11 @@ table.dataTable {
|
|||
}
|
||||
}
|
||||
}
|
||||
.rctable.audit-log {
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
// TRUNCATING
|
||||
// TODO: lisaq: should this possibly be moved out of tables.less?
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
|
||||
pyroutes.register('admin_home', '/_admin', []);
|
||||
pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
|
||||
pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
|
||||
pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
|
||||
pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
|
||||
pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
|
||||
|
|
|
|||
112
rhodecode/templates/admin/admin_audit_log_entry.mako
Normal file
112
rhodecode/templates/admin/admin_audit_log_entry.mako
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
<%inherit file="/base/base.mako"/>
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
|
||||
<%def name="title()">
|
||||
${_('Admin audit log entry')}
|
||||
%if c.rhodecode_name:
|
||||
· ${h.branding(c.rhodecode_name)}
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<%def name="breadcrumbs_links()">
|
||||
${_('Audit long entry')} ${c.audit_log_entry.entry_id}
|
||||
</%def>
|
||||
|
||||
<%def name="menu_bar_nav()">
|
||||
${self.menu_items(active='admin')}
|
||||
</%def>
|
||||
<%def name="main()">
|
||||
<div class="box">
|
||||
<!-- box / title -->
|
||||
<div class="title">
|
||||
${self.breadcrumbs()}
|
||||
</div>
|
||||
<!-- end box / title -->
|
||||
<div class="table">
|
||||
<div id="user_log">
|
||||
<table class="rctable audit-log">
|
||||
<tr>
|
||||
<td>
|
||||
${_('User')}:
|
||||
</td>
|
||||
<td>
|
||||
%if c.audit_log_entry.user is not None:
|
||||
${base.gravatar_with_user(c.audit_log_entry.user.email)}
|
||||
%else:
|
||||
${c.audit_log_entry.username}
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
${_('Date')}:
|
||||
</td>
|
||||
<td>
|
||||
${h.format_date(c.audit_log_entry.action_date)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
${_('IP')}:
|
||||
</td>
|
||||
<td>
|
||||
${c.audit_log_entry.user_ip}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
${_('Action')}:
|
||||
</td>
|
||||
<td>
|
||||
% if c.audit_log_entry.version == c.audit_log_entry.VERSION_1:
|
||||
${h.action_parser(l)[0]()}
|
||||
% else:
|
||||
${h.literal(c.audit_log_entry.action)}
|
||||
% endif
|
||||
|
||||
<div class="journal_action_params">
|
||||
% if c.audit_log_entry.version == c.audit_log_entry.VERSION_1:
|
||||
${h.literal(h.action_parser(l)[1]())}
|
||||
% endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
${_('Action Data')}:
|
||||
</td>
|
||||
<td class="td-journalaction">
|
||||
% if c.audit_log_entry.version == c.audit_log_entry.VERSION_2:
|
||||
<div>
|
||||
<pre>${h.json.dumps(c.audit_log_entry.action_data, indent=4, sort_keys=True)}</pre>
|
||||
</div>
|
||||
% else:
|
||||
<pre title="${_('data not available for v1 entries type')}">-</pre>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
${_('Repository')}:
|
||||
</td>
|
||||
<td class="td-journalaction">
|
||||
%if c.audit_log_entry.repository is not None:
|
||||
${h.link_to(c.audit_log_entry.repository.repo_name, h.route_path('repo_summary',repo_name=c.audit_log_entry.repository.repo_name))}
|
||||
%else:
|
||||
${c.audit_log_entry.repository_name or '-'}
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#j_filter').autoGrowInput();
|
||||
</script>
|
||||
</%def>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
%if c.audit_logs:
|
||||
<table class="rctable admin_log">
|
||||
<tr>
|
||||
<th>${_('Uid')}</th>
|
||||
<th>${_('Username')}</th>
|
||||
<th>${_('Action')}</th>
|
||||
<th>${_('Action Data')}</th>
|
||||
|
|
@ -13,6 +14,9 @@
|
|||
|
||||
%for cnt,l in enumerate(c.audit_logs):
|
||||
<tr class="parity${cnt%2}">
|
||||
<td class="td-col">
|
||||
<a href="${h.route_path('admin_audit_log_entry', audit_log_id=l.entry_id)}">${l.entry_id}</a>
|
||||
</td>
|
||||
<td class="td-user">
|
||||
%if l.user is not None:
|
||||
${base.gravatar_with_user(l.user.email)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue