##// END OF EJS Templates
audit-logs: allow showing individual entries for audit log.
marcink -
r2110:56ff7f71 default
parent child Browse files
Show More
@@ -0,0 +1,112 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
3 <%namespace name="base" file="/base/base.mako"/>
4
5 <%def name="title()">
6 ${_('Admin audit log entry')}
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
10 </%def>
11
12 <%def name="breadcrumbs_links()">
13 ${_('Audit long entry')} ${c.audit_log_entry.entry_id}
14 </%def>
15
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
18 </%def>
19 <%def name="main()">
20 <div class="box">
21 <!-- box / title -->
22 <div class="title">
23 ${self.breadcrumbs()}
24 </div>
25 <!-- end box / title -->
26 <div class="table">
27 <div id="user_log">
28 <table class="rctable audit-log">
29 <tr>
30 <td>
31 ${_('User')}:
32 </td>
33 <td>
34 %if c.audit_log_entry.user is not None:
35 ${base.gravatar_with_user(c.audit_log_entry.user.email)}
36 %else:
37 ${c.audit_log_entry.username}
38 %endif
39 </td>
40 </tr>
41 <tr>
42 <td>
43 ${_('Date')}:
44 </td>
45 <td>
46 ${h.format_date(c.audit_log_entry.action_date)}
47 </td>
48 </tr>
49 <tr>
50 <td>
51 ${_('IP')}:
52 </td>
53 <td>
54 ${c.audit_log_entry.user_ip}
55 </td>
56 </tr>
57
58 <tr>
59 <td>
60 ${_('Action')}:
61 </td>
62 <td>
63 % if c.audit_log_entry.version == c.audit_log_entry.VERSION_1:
64 ${h.action_parser(l)[0]()}
65 % else:
66 ${h.literal(c.audit_log_entry.action)}
67 % endif
68
69 <div class="journal_action_params">
70 % if c.audit_log_entry.version == c.audit_log_entry.VERSION_1:
71 ${h.literal(h.action_parser(l)[1]())}
72 % endif
73 </div>
74 </td>
75 </tr>
76 <tr>
77 <td>
78 ${_('Action Data')}:
79 </td>
80 <td class="td-journalaction">
81 % if c.audit_log_entry.version == c.audit_log_entry.VERSION_2:
82 <div>
83 <pre>${h.json.dumps(c.audit_log_entry.action_data, indent=4, sort_keys=True)}</pre>
84 </div>
85 % else:
86 <pre title="${_('data not available for v1 entries type')}">-</pre>
87 % endif
88 </td>
89 </tr>
90 <tr>
91 <td>
92 ${_('Repository')}:
93 </td>
94 <td class="td-journalaction">
95 %if c.audit_log_entry.repository is not None:
96 ${h.link_to(c.audit_log_entry.repository.repo_name, h.route_path('repo_summary',repo_name=c.audit_log_entry.repository.repo_name))}
97 %else:
98 ${c.audit_log_entry.repository_name or '-'}
99 %endif
100 </td>
101 </tr>
102
103 </table>
104
105 </div>
106 </div>
107 </div>
108
109 <script>
110 $('#j_filter').autoGrowInput();
111 </script>
112 </%def>
@@ -34,6 +34,10 b' def admin_routes(config):'
34 pattern='/audit_logs')
34 pattern='/audit_logs')
35
35
36 config.add_route(
36 config.add_route(
37 name='admin_audit_log_entry',
38 pattern='/audit_logs/{audit_log_id}')
39
40 config.add_route(
37 name='pull_requests_global_0', # backward compat
41 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:\d+}')
42 pattern='/pull_requests/{pull_request_id:\d+}')
39 config.add_route(
43 config.add_route(
@@ -20,6 +20,7 b''
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPNotFound
23 from pyramid.view import view_config
24 from pyramid.view import view_config
24
25
25 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
@@ -70,3 +71,21 b' class AdminAuditLogsView(BaseAppView):'
70 c.audit_logs = Page(users_log, page=p, items_per_page=10,
71 c.audit_logs = Page(users_log, page=p, items_per_page=10,
71 url=url_generator)
72 url=url_generator)
72 return self._get_template_context(c)
73 return self._get_template_context(c)
74
75 @LoginRequired()
76 @HasPermissionAllDecorator('hg.admin')
77 @view_config(
78 route_name='admin_audit_log_entry', request_method='GET',
79 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
80 def admin_audit_log_entry(self):
81 c = self.load_default_context()
82 audit_log_id = self.request.matchdict['audit_log_id']
83
84 c.audit_log_entry = UserLog.query()\
85 .options(joinedload(UserLog.user))\
86 .options(joinedload(UserLog.repository))\
87 .filter(UserLog.user_log_id == audit_log_id).scalar()
88 if not c.audit_log_entry:
89 raise HTTPNotFound()
90
91 return self._get_template_context(c)
@@ -133,9 +133,6 b' def _store_log(action_name, action_data,'
133
133
134 user_log.action_date = datetime.datetime.now()
134 user_log.action_date = datetime.datetime.now()
135
135
136 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
137 action_name, user_id, username, ip_address)
138
139 return user_log
136 return user_log
140
137
141
138
@@ -239,19 +236,27 b' def store(action, user, action_data=None'
239 repository_id = getattr(
236 repository_id = getattr(
240 Repository.get_by_repo_name(repository_name), 'repo_id', None)
237 Repository.get_by_repo_name(repository_name), 'repo_id', None)
241
238
239 action_name = safe_unicode(action)
240 ip_address = safe_unicode(ip_addr)
241
242 user_log = _store_log(
242 user_log = _store_log(
243 action_name=safe_unicode(action),
243 action_name=action_name,
244 action_data=action_data or {},
244 action_data=action_data or {},
245 user_id=user_id,
245 user_id=user_id,
246 username=username,
246 username=username,
247 user_data=user_data or {},
247 user_data=user_data or {},
248 ip_address=safe_unicode(ip_addr),
248 ip_address=ip_address,
249 repository_id=repository_id,
249 repository_id=repository_id,
250 repository_name=repository_name
250 repository_name=repository_name
251 )
251 )
252
252 sa_session.add(user_log)
253 sa_session.add(user_log)
253 if commit:
254 if commit:
254 sa_session.commit()
255 sa_session.commit()
255
256
257 entry_id = user_log.entry_id or ''
258 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
259 entry_id, action_name, user_id, username, ip_address)
260
256 except Exception:
261 except Exception:
257 log.exception('AUDIT: failed to store audit log')
262 log.exception('AUDIT: failed to store audit log')
@@ -1252,6 +1252,10 b' class UserLog(Base, BaseModel):'
1252 'action': self.action,
1252 'action': self.action,
1253 }
1253 }
1254
1254
1255 @hybrid_property
1256 def entry_id(self):
1257 return self.user_log_id
1258
1255 @property
1259 @property
1256 def action_as_day(self):
1260 def action_as_day(self):
1257 return datetime.date(*self.action_date.timetuple()[:3])
1261 return datetime.date(*self.action_date.timetuple()[:3])
@@ -250,6 +250,11 b' table.dataTable {'
250 }
250 }
251 }
251 }
252 }
252 }
253 .rctable.audit-log {
254 td {
255 vertical-align: top;
256 }
257 }
253
258
254 // TRUNCATING
259 // TRUNCATING
255 // TODO: lisaq: should this possibly be moved out of tables.less?
260 // TODO: lisaq: should this possibly be moved out of tables.less?
@@ -38,6 +38,7 b' function registerRCRoutes() {'
38 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
39 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_home', '/_admin', []);
40 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
41 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
@@ -3,6 +3,7 b''
3 %if c.audit_logs:
3 %if c.audit_logs:
4 <table class="rctable admin_log">
4 <table class="rctable admin_log">
5 <tr>
5 <tr>
6 <th>${_('Uid')}</th>
6 <th>${_('Username')}</th>
7 <th>${_('Username')}</th>
7 <th>${_('Action')}</th>
8 <th>${_('Action')}</th>
8 <th>${_('Action Data')}</th>
9 <th>${_('Action Data')}</th>
@@ -13,6 +14,9 b''
13
14
14 %for cnt,l in enumerate(c.audit_logs):
15 %for cnt,l in enumerate(c.audit_logs):
15 <tr class="parity${cnt%2}">
16 <tr class="parity${cnt%2}">
17 <td class="td-col">
18 <a href="${h.route_path('admin_audit_log_entry', audit_log_id=l.entry_id)}">${l.entry_id}</a>
19 </td>
16 <td class="td-user">
20 <td class="td-user">
17 %if l.user is not None:
21 %if l.user is not None:
18 ${base.gravatar_with_user(l.user.email)}
22 ${base.gravatar_with_user(l.user.email)}
General Comments 0
You need to be logged in to leave comments. Login now