##// END OF EJS Templates
user-audit: share same template for rendering audit logs between user and admin views.
marcink -
r1696:089e4a01 default
parent child Browse files
Show More
@@ -0,0 +1,60 b''
1 <%namespace name="base" file="/base/base.mako"/>
2
3 %if c.audit_logs:
4 <table class="rctable admin_log">
5 <tr>
6 <th>${_('Username')}</th>
7 <th>${_('Action')}</th>
8 <th>${_('Action Data')}</th>
9 <th>${_('Repository')}</th>
10 <th>${_('Date')}</th>
11 <th>${_('IP')}</th>
12 </tr>
13
14 %for cnt,l in enumerate(c.audit_logs):
15 <tr class="parity${cnt%2}">
16 <td class="td-user">
17 %if l.user is not None:
18 ${base.gravatar_with_user(l.user.email)}
19 %else:
20 ${l.username}
21 %endif
22 </td>
23 <td class="td-journalaction">
24 % if l.version == l.VERSION_1:
25 ${h.action_parser(l)[0]()}
26 % else:
27 ${h.literal(l.action)}
28 % endif
29
30 <div class="journal_action_params">
31 % if l.version == l.VERSION_1:
32 ${h.literal(h.action_parser(l)[1]())}
33 % endif
34 </div>
35 </td>
36 <td>
37 % if l.version == l.VERSION_2:
38 ${l.action_data}
39 % endif
40 </td>
41 <td class="td-componentname">
42 %if l.repository is not None:
43 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
44 %else:
45 ${l.repository_name}
46 %endif
47 </td>
48
49 <td class="td-time">${h.format_date(l.action_date)}</td>
50 <td class="td-ip">${l.user_ip}</td>
51 </tr>
52 %endfor
53 </table>
54
55 <div class="pagination-wh pagination-left">
56 ${c.audit_logs.pager('$link_previous ~2~ $link_next')}
57 </div>
58 %else:
59 ${_('No actions yet')}
60 %endif No newline at end of file
@@ -1,307 +1,307 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23
23
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from sqlalchemy.sql.functions import coalesce
26 from sqlalchemy.sql.functions import coalesce
27
27
28 from rhodecode.lib.helpers import Page
28 from rhodecode.lib.helpers import Page
29 from rhodecode_tools.lib.ext_json import json
29 from rhodecode_tools.lib.ext_json import json
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.utils import PartialRenderer
35 from rhodecode.lib.utils import PartialRenderer
36 from rhodecode.lib.utils2 import safe_int, safe_unicode
36 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.db import User, or_
40 from rhodecode.model.db import User, or_
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class AdminUsersView(BaseAppView, DataGridAppView):
46 class AdminUsersView(BaseAppView, DataGridAppView):
47 ALLOW_SCOPED_TOKENS = False
47 ALLOW_SCOPED_TOKENS = False
48 """
48 """
49 This view has alternative version inside EE, if modified please take a look
49 This view has alternative version inside EE, if modified please take a look
50 in there as well.
50 in there as well.
51 """
51 """
52
52
53 def load_default_context(self):
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
56 self._register_global_c(c)
56 self._register_global_c(c)
57 return c
57 return c
58
58
59 def _redirect_for_default_user(self, username):
59 def _redirect_for_default_user(self, username):
60 _ = self.request.translate
60 _ = self.request.translate
61 if username == User.DEFAULT_USER:
61 if username == User.DEFAULT_USER:
62 h.flash(_("You can't edit this user"), category='warning')
62 h.flash(_("You can't edit this user"), category='warning')
63 # TODO(marcink): redirect to 'users' admin panel once this
63 # TODO(marcink): redirect to 'users' admin panel once this
64 # is a pyramid view
64 # is a pyramid view
65 raise HTTPFound('/')
65 raise HTTPFound('/')
66
66
67 @HasPermissionAllDecorator('hg.admin')
67 @HasPermissionAllDecorator('hg.admin')
68 @view_config(
68 @view_config(
69 route_name='users', request_method='GET',
69 route_name='users', request_method='GET',
70 renderer='rhodecode:templates/admin/users/users.mako')
70 renderer='rhodecode:templates/admin/users/users.mako')
71 def users_list(self):
71 def users_list(self):
72 c = self.load_default_context()
72 c = self.load_default_context()
73 return self._get_template_context(c)
73 return self._get_template_context(c)
74
74
75 @HasPermissionAllDecorator('hg.admin')
75 @HasPermissionAllDecorator('hg.admin')
76 @view_config(
76 @view_config(
77 # renderer defined below
77 # renderer defined below
78 route_name='users_data', request_method='GET',
78 route_name='users_data', request_method='GET',
79 renderer='json_ext', xhr=True)
79 renderer='json_ext', xhr=True)
80 def users_list_data(self):
80 def users_list_data(self):
81 draw, start, limit = self._extract_chunk(self.request)
81 draw, start, limit = self._extract_chunk(self.request)
82 search_q, order_by, order_dir = self._extract_ordering(self.request)
82 search_q, order_by, order_dir = self._extract_ordering(self.request)
83
83
84 _render = PartialRenderer('data_table/_dt_elements.mako')
84 _render = PartialRenderer('data_table/_dt_elements.mako')
85
85
86 def user_actions(user_id, username):
86 def user_actions(user_id, username):
87 return _render("user_actions", user_id, username)
87 return _render("user_actions", user_id, username)
88
88
89 users_data_total_count = User.query()\
89 users_data_total_count = User.query()\
90 .filter(User.username != User.DEFAULT_USER) \
90 .filter(User.username != User.DEFAULT_USER) \
91 .count()
91 .count()
92
92
93 # json generate
93 # json generate
94 base_q = User.query().filter(User.username != User.DEFAULT_USER)
94 base_q = User.query().filter(User.username != User.DEFAULT_USER)
95
95
96 if search_q:
96 if search_q:
97 like_expression = u'%{}%'.format(safe_unicode(search_q))
97 like_expression = u'%{}%'.format(safe_unicode(search_q))
98 base_q = base_q.filter(or_(
98 base_q = base_q.filter(or_(
99 User.username.ilike(like_expression),
99 User.username.ilike(like_expression),
100 User._email.ilike(like_expression),
100 User._email.ilike(like_expression),
101 User.name.ilike(like_expression),
101 User.name.ilike(like_expression),
102 User.lastname.ilike(like_expression),
102 User.lastname.ilike(like_expression),
103 ))
103 ))
104
104
105 users_data_total_filtered_count = base_q.count()
105 users_data_total_filtered_count = base_q.count()
106
106
107 sort_col = getattr(User, order_by, None)
107 sort_col = getattr(User, order_by, None)
108 if sort_col:
108 if sort_col:
109 if order_dir == 'asc':
109 if order_dir == 'asc':
110 # handle null values properly to order by NULL last
110 # handle null values properly to order by NULL last
111 if order_by in ['last_activity']:
111 if order_by in ['last_activity']:
112 sort_col = coalesce(sort_col, datetime.date.max)
112 sort_col = coalesce(sort_col, datetime.date.max)
113 sort_col = sort_col.asc()
113 sort_col = sort_col.asc()
114 else:
114 else:
115 # handle null values properly to order by NULL last
115 # handle null values properly to order by NULL last
116 if order_by in ['last_activity']:
116 if order_by in ['last_activity']:
117 sort_col = coalesce(sort_col, datetime.date.min)
117 sort_col = coalesce(sort_col, datetime.date.min)
118 sort_col = sort_col.desc()
118 sort_col = sort_col.desc()
119
119
120 base_q = base_q.order_by(sort_col)
120 base_q = base_q.order_by(sort_col)
121 base_q = base_q.offset(start).limit(limit)
121 base_q = base_q.offset(start).limit(limit)
122
122
123 users_list = base_q.all()
123 users_list = base_q.all()
124
124
125 users_data = []
125 users_data = []
126 for user in users_list:
126 for user in users_list:
127 users_data.append({
127 users_data.append({
128 "username": h.gravatar_with_user(user.username),
128 "username": h.gravatar_with_user(user.username),
129 "email": user.email,
129 "email": user.email,
130 "first_name": h.escape(user.name),
130 "first_name": h.escape(user.name),
131 "last_name": h.escape(user.lastname),
131 "last_name": h.escape(user.lastname),
132 "last_login": h.format_date(user.last_login),
132 "last_login": h.format_date(user.last_login),
133 "last_activity": h.format_date(user.last_activity),
133 "last_activity": h.format_date(user.last_activity),
134 "active": h.bool2icon(user.active),
134 "active": h.bool2icon(user.active),
135 "active_raw": user.active,
135 "active_raw": user.active,
136 "admin": h.bool2icon(user.admin),
136 "admin": h.bool2icon(user.admin),
137 "extern_type": user.extern_type,
137 "extern_type": user.extern_type,
138 "extern_name": user.extern_name,
138 "extern_name": user.extern_name,
139 "action": user_actions(user.user_id, user.username),
139 "action": user_actions(user.user_id, user.username),
140 })
140 })
141
141
142 data = ({
142 data = ({
143 'draw': draw,
143 'draw': draw,
144 'data': users_data,
144 'data': users_data,
145 'recordsTotal': users_data_total_count,
145 'recordsTotal': users_data_total_count,
146 'recordsFiltered': users_data_total_filtered_count,
146 'recordsFiltered': users_data_total_filtered_count,
147 })
147 })
148
148
149 return data
149 return data
150
150
151 @LoginRequired()
151 @LoginRequired()
152 @HasPermissionAllDecorator('hg.admin')
152 @HasPermissionAllDecorator('hg.admin')
153 @view_config(
153 @view_config(
154 route_name='edit_user_auth_tokens', request_method='GET',
154 route_name='edit_user_auth_tokens', request_method='GET',
155 renderer='rhodecode:templates/admin/users/user_edit.mako')
155 renderer='rhodecode:templates/admin/users/user_edit.mako')
156 def auth_tokens(self):
156 def auth_tokens(self):
157 _ = self.request.translate
157 _ = self.request.translate
158 c = self.load_default_context()
158 c = self.load_default_context()
159
159
160 user_id = self.request.matchdict.get('user_id')
160 user_id = self.request.matchdict.get('user_id')
161 c.user = User.get_or_404(user_id, pyramid_exc=True)
161 c.user = User.get_or_404(user_id, pyramid_exc=True)
162 self._redirect_for_default_user(c.user.username)
162 self._redirect_for_default_user(c.user.username)
163
163
164 c.active = 'auth_tokens'
164 c.active = 'auth_tokens'
165
165
166 c.lifetime_values = [
166 c.lifetime_values = [
167 (str(-1), _('forever')),
167 (str(-1), _('forever')),
168 (str(5), _('5 minutes')),
168 (str(5), _('5 minutes')),
169 (str(60), _('1 hour')),
169 (str(60), _('1 hour')),
170 (str(60 * 24), _('1 day')),
170 (str(60 * 24), _('1 day')),
171 (str(60 * 24 * 30), _('1 month')),
171 (str(60 * 24 * 30), _('1 month')),
172 ]
172 ]
173 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
173 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
174 c.role_values = [
174 c.role_values = [
175 (x, AuthTokenModel.cls._get_role_name(x))
175 (x, AuthTokenModel.cls._get_role_name(x))
176 for x in AuthTokenModel.cls.ROLES]
176 for x in AuthTokenModel.cls.ROLES]
177 c.role_options = [(c.role_values, _("Role"))]
177 c.role_options = [(c.role_values, _("Role"))]
178 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
178 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
179 c.user.user_id, show_expired=True)
179 c.user.user_id, show_expired=True)
180 return self._get_template_context(c)
180 return self._get_template_context(c)
181
181
182 def maybe_attach_token_scope(self, token):
182 def maybe_attach_token_scope(self, token):
183 # implemented in EE edition
183 # implemented in EE edition
184 pass
184 pass
185
185
186 @LoginRequired()
186 @LoginRequired()
187 @HasPermissionAllDecorator('hg.admin')
187 @HasPermissionAllDecorator('hg.admin')
188 @CSRFRequired()
188 @CSRFRequired()
189 @view_config(
189 @view_config(
190 route_name='edit_user_auth_tokens_add', request_method='POST')
190 route_name='edit_user_auth_tokens_add', request_method='POST')
191 def auth_tokens_add(self):
191 def auth_tokens_add(self):
192 _ = self.request.translate
192 _ = self.request.translate
193 c = self.load_default_context()
193 c = self.load_default_context()
194
194
195 user_id = self.request.matchdict.get('user_id')
195 user_id = self.request.matchdict.get('user_id')
196 c.user = User.get_or_404(user_id, pyramid_exc=True)
196 c.user = User.get_or_404(user_id, pyramid_exc=True)
197 self._redirect_for_default_user(c.user.username)
197 self._redirect_for_default_user(c.user.username)
198
198
199 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
199 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
200 description = self.request.POST.get('description')
200 description = self.request.POST.get('description')
201 role = self.request.POST.get('role')
201 role = self.request.POST.get('role')
202
202
203 token = AuthTokenModel().create(
203 token = AuthTokenModel().create(
204 c.user.user_id, description, lifetime, role)
204 c.user.user_id, description, lifetime, role)
205 self.maybe_attach_token_scope(token)
205 self.maybe_attach_token_scope(token)
206 Session().commit()
206 Session().commit()
207
207
208 h.flash(_("Auth token successfully created"), category='success')
208 h.flash(_("Auth token successfully created"), category='success')
209 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
209 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
210
210
211 @LoginRequired()
211 @LoginRequired()
212 @HasPermissionAllDecorator('hg.admin')
212 @HasPermissionAllDecorator('hg.admin')
213 @CSRFRequired()
213 @CSRFRequired()
214 @view_config(
214 @view_config(
215 route_name='edit_user_auth_tokens_delete', request_method='POST')
215 route_name='edit_user_auth_tokens_delete', request_method='POST')
216 def auth_tokens_delete(self):
216 def auth_tokens_delete(self):
217 _ = self.request.translate
217 _ = self.request.translate
218 c = self.load_default_context()
218 c = self.load_default_context()
219
219
220 user_id = self.request.matchdict.get('user_id')
220 user_id = self.request.matchdict.get('user_id')
221 c.user = User.get_or_404(user_id, pyramid_exc=True)
221 c.user = User.get_or_404(user_id, pyramid_exc=True)
222 self._redirect_for_default_user(c.user.username)
222 self._redirect_for_default_user(c.user.username)
223
223
224 del_auth_token = self.request.POST.get('del_auth_token')
224 del_auth_token = self.request.POST.get('del_auth_token')
225
225
226 if del_auth_token:
226 if del_auth_token:
227 AuthTokenModel().delete(del_auth_token, c.user.user_id)
227 AuthTokenModel().delete(del_auth_token, c.user.user_id)
228 Session().commit()
228 Session().commit()
229 h.flash(_("Auth token successfully deleted"), category='success')
229 h.flash(_("Auth token successfully deleted"), category='success')
230
230
231 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
231 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
232
232
233 @LoginRequired()
233 @LoginRequired()
234 @HasPermissionAllDecorator('hg.admin')
234 @HasPermissionAllDecorator('hg.admin')
235 @view_config(
235 @view_config(
236 route_name='edit_user_groups_management', request_method='GET',
236 route_name='edit_user_groups_management', request_method='GET',
237 renderer='rhodecode:templates/admin/users/user_edit.mako')
237 renderer='rhodecode:templates/admin/users/user_edit.mako')
238 def groups_management(self):
238 def groups_management(self):
239 c = self.load_default_context()
239 c = self.load_default_context()
240
240
241 user_id = self.request.matchdict.get('user_id')
241 user_id = self.request.matchdict.get('user_id')
242 c.user = User.get_or_404(user_id, pyramid_exc=True)
242 c.user = User.get_or_404(user_id, pyramid_exc=True)
243 c.data = c.user.group_member
243 c.data = c.user.group_member
244 self._redirect_for_default_user(c.user.username)
244 self._redirect_for_default_user(c.user.username)
245 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
245 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
246 for group in c.user.group_member]
246 for group in c.user.group_member]
247 c.groups = json.dumps(groups)
247 c.groups = json.dumps(groups)
248 c.active = 'groups'
248 c.active = 'groups'
249
249
250 return self._get_template_context(c)
250 return self._get_template_context(c)
251
251
252 @LoginRequired()
252 @LoginRequired()
253 @HasPermissionAllDecorator('hg.admin')
253 @HasPermissionAllDecorator('hg.admin')
254 @view_config(
254 @view_config(
255 route_name='edit_user_groups_management_updates', request_method='POST')
255 route_name='edit_user_groups_management_updates', request_method='POST')
256 def groups_management_updates(self):
256 def groups_management_updates(self):
257 _ = self.request.translate
257 _ = self.request.translate
258 c = self.load_default_context()
258 c = self.load_default_context()
259
259
260 user_id = self.request.matchdict.get('user_id')
260 user_id = self.request.matchdict.get('user_id')
261 c.user = User.get_or_404(user_id, pyramid_exc=True)
261 c.user = User.get_or_404(user_id, pyramid_exc=True)
262 self._redirect_for_default_user(c.user.username)
262 self._redirect_for_default_user(c.user.username)
263
263
264 users_groups = set(self.request.POST.getall('users_group_id'))
264 users_groups = set(self.request.POST.getall('users_group_id'))
265 users_groups_model = []
265 users_groups_model = []
266
266
267 for ugid in users_groups:
267 for ugid in users_groups:
268 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
268 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
269 user_group_model = UserGroupModel()
269 user_group_model = UserGroupModel()
270 user_group_model.change_groups(c.user, users_groups_model)
270 user_group_model.change_groups(c.user, users_groups_model)
271
271
272 Session().commit()
272 Session().commit()
273 c.active = 'user_groups_management'
273 c.active = 'user_groups_management'
274 h.flash(_("Groups successfully changed"), category='success')
274 h.flash(_("Groups successfully changed"), category='success')
275
275
276 return HTTPFound(h.route_path(
276 return HTTPFound(h.route_path(
277 'edit_user_groups_management', user_id=user_id))
277 'edit_user_groups_management', user_id=user_id))
278
278
279 @LoginRequired()
279 @LoginRequired()
280 @HasPermissionAllDecorator('hg.admin')
280 @HasPermissionAllDecorator('hg.admin')
281 @view_config(
281 @view_config(
282 route_name='edit_user_audit_logs', request_method='GET',
282 route_name='edit_user_audit_logs', request_method='GET',
283 renderer='rhodecode:templates/admin/users/user_edit.mako')
283 renderer='rhodecode:templates/admin/users/user_edit.mako')
284 def user_audit_logs(self):
284 def user_audit_logs(self):
285 _ = self.request.translate
285 _ = self.request.translate
286 c = self.load_default_context()
286 c = self.load_default_context()
287
287
288 user_id = self.request.matchdict.get('user_id')
288 user_id = self.request.matchdict.get('user_id')
289 c.user = User.get_or_404(user_id, pyramid_exc=True)
289 c.user = User.get_or_404(user_id, pyramid_exc=True)
290 self._redirect_for_default_user(c.user.username)
290 self._redirect_for_default_user(c.user.username)
291 c.active = 'audit'
291 c.active = 'audit'
292
292
293 p = safe_int(self.request.GET.get('page', 1), 1)
293 p = safe_int(self.request.GET.get('page', 1), 1)
294
294
295 filter_term = self.request.GET.get('filter')
295 filter_term = self.request.GET.get('filter')
296 c.user_log = UserModel().get_user_log(c.user, filter_term)
296 user_log = UserModel().get_user_log(c.user, filter_term)
297
297
298 def url_generator(**kw):
298 def url_generator(**kw):
299 if filter_term:
299 if filter_term:
300 kw['filter'] = filter_term
300 kw['filter'] = filter_term
301 return self.request.current_route_path(_query=kw)
301 return self.request.current_route_path(_query=kw)
302
302
303 c.user_log = Page(c.user_log, page=p, items_per_page=10,
303 c.audit_logs = Page(user_log, page=p, items_per_page=10,
304 url=url_generator)
304 url=url_generator)
305 c.filter_term = filter_term
305 c.filter_term = filter_term
306 return self._get_template_context(c)
306 return self._get_template_context(c)
307
307
@@ -1,89 +1,89 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Controller for Admin panel of RhodeCode Enterprise
22 Controller for Admin panel of RhodeCode Enterprise
23 """
23 """
24
24
25
25
26 import logging
26 import logging
27
27
28 from pylons import request, tmpl_context as c, url
28 from pylons import request, tmpl_context as c, url
29 from pylons.controllers.util import redirect
29 from pylons.controllers.util import redirect
30 from sqlalchemy.orm import joinedload
30 from sqlalchemy.orm import joinedload
31
31
32 from rhodecode.model.db import UserLog, PullRequest
32 from rhodecode.model.db import UserLog, PullRequest
33 from rhodecode.lib.user_log_filter import user_log_filter
33 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
35 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.utils2 import safe_int
36 from rhodecode.lib.utils2 import safe_int
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import Page
38
38
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class AdminController(BaseController):
43 class AdminController(BaseController):
44
44
45 @LoginRequired()
45 @LoginRequired()
46 def __before__(self):
46 def __before__(self):
47 super(AdminController, self).__before__()
47 super(AdminController, self).__before__()
48
48
49 @HasPermissionAllDecorator('hg.admin')
49 @HasPermissionAllDecorator('hg.admin')
50 def index(self):
50 def index(self):
51 users_log = UserLog.query()\
51 users_log = UserLog.query()\
52 .options(joinedload(UserLog.user))\
52 .options(joinedload(UserLog.user))\
53 .options(joinedload(UserLog.repository))
53 .options(joinedload(UserLog.repository))
54
54
55 # FILTERING
55 # FILTERING
56 c.search_term = request.GET.get('filter')
56 c.search_term = request.GET.get('filter')
57 try:
57 try:
58 users_log = user_log_filter(users_log, c.search_term)
58 users_log = user_log_filter(users_log, c.search_term)
59 except Exception:
59 except Exception:
60 # we want this to crash for now
60 # we want this to crash for now
61 raise
61 raise
62
62
63 users_log = users_log.order_by(UserLog.action_date.desc())
63 users_log = users_log.order_by(UserLog.action_date.desc())
64
64
65 p = safe_int(request.GET.get('page', 1), 1)
65 p = safe_int(request.GET.get('page', 1), 1)
66
66
67 def url_generator(**kw):
67 def url_generator(**kw):
68 return url.current(filter=c.search_term, **kw)
68 return url.current(filter=c.search_term, **kw)
69
69
70 c.users_log = Page(users_log, page=p, items_per_page=10,
70 c.audit_logs = Page(users_log, page=p, items_per_page=10,
71 url=url_generator)
71 url=url_generator)
72 c.log_data = render('admin/admin_log.mako')
72 c.log_data = render('admin/admin_log.mako')
73
73
74 if request.is_xhr:
74 if request.is_xhr:
75 return c.log_data
75 return c.log_data
76 return render('admin/admin.mako')
76 return render('admin/admin.mako')
77
77
78 # global redirect doesn't need permissions
78 # global redirect doesn't need permissions
79 def pull_requests(self, pull_request_id):
79 def pull_requests(self, pull_request_id):
80 """
80 """
81 Global redirect for Pull Requests
81 Global redirect for Pull Requests
82
82
83 :param pull_request_id: id of pull requests in the system
83 :param pull_request_id: id of pull requests in the system
84 """
84 """
85 pull_request = PullRequest.get_or_404(pull_request_id)
85 pull_request = PullRequest.get_or_404(pull_request_id)
86 repo_name = pull_request.target_repo.repo_name
86 repo_name = pull_request.target_repo.repo_name
87 return redirect(url(
87 return redirect(url(
88 'pullrequest_show', repo_name=repo_name,
88 'pullrequest_show', repo_name=repo_name,
89 pull_request_id=pull_request_id))
89 pull_request_id=pull_request_id))
@@ -1,40 +1,40 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Admin journal')}
5 ${_('Admin journal')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.form(None, id_="filter_form", method="get")}
12 ${h.form(None, id_="filter_form", method="get")}
13 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or ''}" placeholder="${_('journal filter...')}"/>
13 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or ''}" placeholder="${_('journal filter...')}"/>
14 <input type='submit' value="${_('filter')}" class="btn" />
14 <input type='submit' value="${_('filter')}" class="btn" />
15 ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
15 ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
16 ${h.end_form()}
16 ${h.end_form()}
17 <p class="tooltip filterexample" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
17 <p class="tooltip filterexample" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
18 </%def>
18 </%def>
19
19
20 <%def name="menu_bar_nav()">
20 <%def name="menu_bar_nav()">
21 ${self.menu_items(active='admin')}
21 ${self.menu_items(active='admin')}
22 </%def>
22 </%def>
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <!-- box / title -->
25 <!-- box / title -->
26 <div class="title">
26 <div class="title">
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28 </div>
28 </div>
29 <!-- end box / title -->
29 <!-- end box / title -->
30 <div class="table">
30 <div class="table">
31 <div id="user_log">
31 <div id="user_log">
32 ${c.log_data}
32 ${c.log_data}
33 </div>
33 </div>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <script>
37 <script>
38 $('#j_filter').autoGrowInput();
38 $('#j_filter').autoGrowInput();
39 </script>
39 </script>
40 </%def>
40 </%def>
@@ -1,74 +1,15 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%include file="/admin/admin_log_base.mako" />
3
4 %if c.users_log:
5 <table class="rctable admin_log">
6 <tr>
7 <th>${_('Username')}</th>
8 <th>${_('Action')}</th>
9 <th>${_('Action Data')}</th>
10 <th>${_('Repository')}</th>
11 <th>${_('Date')}</th>
12 <th>${_('IP')}</th>
13 </tr>
14
15 %for cnt,l in enumerate(c.users_log):
16 <tr class="parity${cnt%2}">
17 <td class="td-user">
18 %if l.user is not None:
19 ${base.gravatar_with_user(l.user.email)}
20 %else:
21 ${l.username}
22 %endif
23 </td>
24 <td class="td-journalaction">
25 % if l.version == l.VERSION_1:
26 ${h.action_parser(l)[0]()}
27 % else:
28 ${h.literal(l.action)}
29 % endif
30
31 <div class="journal_action_params">
32 % if l.version == l.VERSION_1:
33 ${h.literal(h.action_parser(l)[1]())}
34 % endif
35 </div>
36 </td>
37 <td>
38 % if l.version == l.VERSION_2:
39 ${l.action_data}
40 % endif
41 </td>
42 <td class="td-componentname">
43 %if l.repository is not None:
44 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
45 %else:
46 ${l.repository_name}
47 %endif
48 </td>
49
50 <td class="td-time">${h.format_date(l.action_date)}</td>
51 <td class="td-ip">${l.user_ip}</td>
52 </tr>
53 %endfor
54 </table>
55
56 <div class="pagination-wh pagination-left">
57 ${c.users_log.pager('$link_previous ~2~ $link_next')}
58 </div>
59 %else:
60 ${_('No actions yet')}
61 %endif
62
3
63 <script type="text/javascript">
4 <script type="text/javascript">
64 $(function(){
5 $(function(){
65 //because this is loaded on every pjax request, it must run only once
6 //because this is loaded on every pjax request, it must run only once
66 //therefore the .one method
7 //therefore the .one method
67 $(document).on('pjax:complete',function(){
8 $(document).on('pjax:complete',function(){
68 show_more_event();
9 show_more_event();
69 });
10 });
70
11
71 $(document).pjax('#user_log .pager_link', '#user_log');
12 $(document).pjax('#user_log .pager_link', '#user_log');
72 });
13 });
73 </script>
14 </script>
74
15
@@ -1,65 +1,22 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4
4
5 <div class="panel panel-default">
5 <div class="panel panel-default">
6 <div class="panel-heading">
6 <div class="panel-heading">
7 <h3 class="panel-title">${_('User Audit Logs')} -
7 <h3 class="panel-title">${_('User Audit Logs')} -
8 ${_ungettext('%s entry', '%s entries', c.user_log.item_count) % (c.user_log.item_count)}
8 ${_ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
9 </h3>
9 </h3>
10 </div>
10 </div>
11 <div class="panel-body">
11 <div class="panel-body">
12
12
13 ${h.form(None, id_="filter_form", method="get")}
13 ${h.form(None, id_="filter_form", method="get")}
14 <input class="q_filter_box ${'' if c.filter_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.filter_term or ''}" placeholder="${_('audit filter...')}"/>
14 <input class="q_filter_box ${'' if c.filter_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.filter_term or ''}" placeholder="${_('audit filter...')}"/>
15 <input type='submit' value="${_('filter')}" class="btn" />
15 <input type='submit' value="${_('filter')}" class="btn" />
16 ${h.end_form()}
16 ${h.end_form()}
17 <p class="tooltip filterexample" style="position: inherit" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
17 <p class="tooltip filterexample" style="position: inherit" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
18
18
19 % if c.user_log:
19 <%include file="/admin/admin_log_base.mako" />
20 <table class="rctable admin_log">
21 <tr>
22 <th>${_('Username')}</th>
23 <th>${_('Action')}</th>
24 <th>${_('Repository')}</th>
25 <th>${_('Date')}</th>
26 <th>${_('From IP')}</th>
27 </tr>
28
29 %for cnt,l in enumerate(c.user_log):
30 <tr class="parity${cnt%2}">
31 <td class="td-user">
32 %if l.user is not None:
33 ${base.gravatar_with_user(l.user.email)}
34 %else:
35 ${l.username}
36 %endif
37 </td>
38 <td class="td-journalaction">${h.action_parser(l)[0]()}
39 <div class="journal_action_params">
40 ${h.literal(h.action_parser(l)[1]())}
41 </div>
42 </td>
43 <td class="td-componentname">
44 %if l.repository is not None:
45 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
46 %else:
47 ${l.repository_name}
48 %endif
49 </td>
50
51 <td class="td-time">${h.format_date(l.action_date)}</td>
52 <td class="td-ip">${l.user_ip}</td>
53 </tr>
54 %endfor
55 </table>
56
57 <div class="pagination-wh pagination-left">
58 ${c.user_log.pager('$link_previous ~2~ $link_next')}
59 </div>
60 % else:
61 ${_('No actions yet')}
62 % endif
63
20
64 </div>
21 </div>
65 </div>
22 </div>
General Comments 0
You need to be logged in to leave comments. Login now