Show More
@@ -0,0 +1,112 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |||
|
4 | # | |||
|
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 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
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/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | import logging | |||
|
22 | ||||
|
23 | from whoosh.qparser.default import QueryParser, query | |||
|
24 | from whoosh.qparser.dateparse import DateParserPlugin | |||
|
25 | from whoosh.fields import (TEXT, Schema, DATETIME) | |||
|
26 | from sqlalchemy.sql.expression import or_, and_, func | |||
|
27 | ||||
|
28 | from rhodecode.model.db import UserLog | |||
|
29 | from rhodecode.lib.utils2 import remove_prefix, remove_suffix | |||
|
30 | ||||
|
31 | # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh | |||
|
32 | # querylang to build sql queries and filter journals | |||
|
33 | JOURNAL_SCHEMA = Schema( | |||
|
34 | username=TEXT(), | |||
|
35 | date=DATETIME(), | |||
|
36 | action=TEXT(), | |||
|
37 | repository=TEXT(), | |||
|
38 | ip=TEXT(), | |||
|
39 | ) | |||
|
40 | ||||
|
41 | log = logging.getLogger(__name__) | |||
|
42 | ||||
|
43 | ||||
|
44 | def user_log_filter(user_log, search_term): | |||
|
45 | """ | |||
|
46 | Filters sqlalchemy user_log based on search_term with whoosh Query language | |||
|
47 | http://packages.python.org/Whoosh/querylang.html | |||
|
48 | ||||
|
49 | :param user_log: | |||
|
50 | :param search_term: | |||
|
51 | """ | |||
|
52 | log.debug('Initial search term: %r' % search_term) | |||
|
53 | qry = None | |||
|
54 | if search_term: | |||
|
55 | qp = QueryParser('repository', schema=JOURNAL_SCHEMA) | |||
|
56 | qp.add_plugin(DateParserPlugin()) | |||
|
57 | qry = qp.parse(unicode(search_term)) | |||
|
58 | log.debug('Filtering using parsed query %r' % qry) | |||
|
59 | ||||
|
60 | def wildcard_handler(col, wc_term): | |||
|
61 | if wc_term.startswith('*') and not wc_term.endswith('*'): | |||
|
62 | # postfix == endswith | |||
|
63 | wc_term = remove_prefix(wc_term, prefix='*') | |||
|
64 | return func.lower(col).endswith(wc_term) | |||
|
65 | elif wc_term.startswith('*') and wc_term.endswith('*'): | |||
|
66 | # wildcard == ilike | |||
|
67 | wc_term = remove_prefix(wc_term, prefix='*') | |||
|
68 | wc_term = remove_suffix(wc_term, suffix='*') | |||
|
69 | return func.lower(col).contains(wc_term) | |||
|
70 | ||||
|
71 | def get_filterion(field, val, term): | |||
|
72 | ||||
|
73 | if field == 'repository': | |||
|
74 | field = getattr(UserLog, 'repository_name') | |||
|
75 | elif field == 'ip': | |||
|
76 | field = getattr(UserLog, 'user_ip') | |||
|
77 | elif field == 'date': | |||
|
78 | field = getattr(UserLog, 'action_date') | |||
|
79 | elif field == 'username': | |||
|
80 | field = getattr(UserLog, 'username') | |||
|
81 | else: | |||
|
82 | field = getattr(UserLog, field) | |||
|
83 | log.debug('filter field: %s val=>%s' % (field, val)) | |||
|
84 | ||||
|
85 | # sql filtering | |||
|
86 | if isinstance(term, query.Wildcard): | |||
|
87 | return wildcard_handler(field, val) | |||
|
88 | elif isinstance(term, query.Prefix): | |||
|
89 | return func.lower(field).startswith(func.lower(val)) | |||
|
90 | elif isinstance(term, query.DateRange): | |||
|
91 | return and_(field >= val[0], field <= val[1]) | |||
|
92 | return func.lower(field) == func.lower(val) | |||
|
93 | ||||
|
94 | if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard, | |||
|
95 | query.DateRange)): | |||
|
96 | if not isinstance(qry, query.And): | |||
|
97 | qry = [qry] | |||
|
98 | for term in qry: | |||
|
99 | field = term.fieldname | |||
|
100 | val = (term.text if not isinstance(term, query.DateRange) | |||
|
101 | else [term.startdate, term.enddate]) | |||
|
102 | user_log = user_log.filter(get_filterion(field, val, term)) | |||
|
103 | elif isinstance(qry, query.Or): | |||
|
104 | filters = [] | |||
|
105 | for term in qry: | |||
|
106 | field = term.fieldname | |||
|
107 | val = (term.text if not isinstance(term, query.DateRange) | |||
|
108 | else [term.startdate, term.enddate]) | |||
|
109 | filters.append(get_filterion(field, val, term)) | |||
|
110 | user_log = user_log.filter(or_(*filters)) | |||
|
111 | ||||
|
112 | return user_log |
@@ -0,0 +1,65 b'' | |||||
|
1 | ## -*- coding: utf-8 -*- | |||
|
2 | <%namespace name="base" file="/base/base.mako"/> | |||
|
3 | ||||
|
4 | ||||
|
5 | <div class="panel panel-default"> | |||
|
6 | <div class="panel-heading"> | |||
|
7 | <h3 class="panel-title">${_('User Audit Logs')} - | |||
|
8 | ${_ungettext('%s entry', '%s entries', c.user_log.item_count) % (c.user_log.item_count)} | |||
|
9 | </h3> | |||
|
10 | </div> | |||
|
11 | <div class="panel-body"> | |||
|
12 | ||||
|
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...')}"/> | |||
|
15 | <input type='submit' value="${_('filter')}" class="btn" /> | |||
|
16 | ${h.end_form()} | |||
|
17 | <p class="tooltip filterexample" style="position: inherit" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p> | |||
|
18 | ||||
|
19 | % if c.user_log: | |||
|
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 | ||||
|
64 | </div> | |||
|
65 | </div> |
@@ -79,6 +79,11 b' def admin_routes(config):' | |||||
79 | name='edit_user_groups_management_updates', |
|
79 | name='edit_user_groups_management_updates', | |
80 | pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates') |
|
80 | pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates') | |
81 |
|
81 | |||
|
82 | # user audit logs | |||
|
83 | config.add_route( | |||
|
84 | name='edit_user_audit_logs', | |||
|
85 | pattern='/users/{user_id:\d+}/edit/audit') | |||
|
86 | ||||
82 |
|
87 | |||
83 | def includeme(config): |
|
88 | def includeme(config): | |
84 | settings = config.get_settings() |
|
89 | settings = config.get_settings() |
@@ -22,6 +22,8 b' import logging' | |||||
22 |
|
22 | |||
23 | from pyramid.httpexceptions import HTTPFound |
|
23 | from pyramid.httpexceptions import HTTPFound | |
24 | from pyramid.view import view_config |
|
24 | from pyramid.view import view_config | |
|
25 | ||||
|
26 | from rhodecode.lib.helpers import Page | |||
25 | from rhodecode_tools.lib.ext_json import json |
|
27 | from rhodecode_tools.lib.ext_json import json | |
26 |
|
28 | |||
27 | from rhodecode.apps._base import BaseAppView |
|
29 | from rhodecode.apps._base import BaseAppView | |
@@ -31,6 +33,7 b' from rhodecode.lib import helpers as h' | |||||
31 | from rhodecode.lib.utils import PartialRenderer |
|
33 | from rhodecode.lib.utils import PartialRenderer | |
32 | from rhodecode.lib.utils2 import safe_int, safe_unicode |
|
34 | from rhodecode.lib.utils2 import safe_int, safe_unicode | |
33 | from rhodecode.model.auth_token import AuthTokenModel |
|
35 | from rhodecode.model.auth_token import AuthTokenModel | |
|
36 | from rhodecode.model.user import UserModel | |||
34 | from rhodecode.model.user_group import UserGroupModel |
|
37 | from rhodecode.model.user_group import UserGroupModel | |
35 | from rhodecode.model.db import User, or_ |
|
38 | from rhodecode.model.db import User, or_ | |
36 | from rhodecode.model.meta import Session |
|
39 | from rhodecode.model.meta import Session | |
@@ -238,7 +241,6 b' class AdminUsersView(BaseAppView):' | |||||
238 |
|
241 | |||
239 | return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id)) |
|
242 | return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id)) | |
240 |
|
243 | |||
241 |
|
||||
242 | @LoginRequired() |
|
244 | @LoginRequired() | |
243 | @HasPermissionAllDecorator('hg.admin') |
|
245 | @HasPermissionAllDecorator('hg.admin') | |
244 | @view_config( |
|
246 | @view_config( | |
@@ -257,7 +259,6 b' class AdminUsersView(BaseAppView):' | |||||
257 |
|
259 | |||
258 | return self._get_template_context(c) |
|
260 | return self._get_template_context(c) | |
259 |
|
261 | |||
260 |
|
||||
261 | @LoginRequired() |
|
262 | @LoginRequired() | |
262 | @HasPermissionAllDecorator('hg.admin') |
|
263 | @HasPermissionAllDecorator('hg.admin') | |
263 | @view_config( |
|
264 | @view_config( | |
@@ -282,4 +283,35 b' class AdminUsersView(BaseAppView):' | |||||
282 | c.active = 'user_groups_management' |
|
283 | c.active = 'user_groups_management' | |
283 | h.flash(_("Groups successfully changed"), category='success') |
|
284 | h.flash(_("Groups successfully changed"), category='success') | |
284 |
|
285 | |||
285 |
return HTTPFound(h.route_path( |
|
286 | return HTTPFound(h.route_path( | |
|
287 | 'edit_user_groups_management', user_id=user_id)) | |||
|
288 | ||||
|
289 | @LoginRequired() | |||
|
290 | @HasPermissionAllDecorator('hg.admin') | |||
|
291 | @view_config( | |||
|
292 | route_name='edit_user_audit_logs', request_method='GET', | |||
|
293 | renderer='rhodecode:templates/admin/users/user_edit.mako') | |||
|
294 | def user_audit_logs(self): | |||
|
295 | _ = self.request.translate | |||
|
296 | c = self.load_default_context() | |||
|
297 | ||||
|
298 | user_id = self.request.matchdict.get('user_id') | |||
|
299 | c.user = User.get_or_404(user_id, pyramid_exc=True) | |||
|
300 | self._redirect_for_default_user(c.user.username) | |||
|
301 | c.active = 'audit' | |||
|
302 | ||||
|
303 | p = safe_int(self.request.GET.get('page', 1), 1) | |||
|
304 | ||||
|
305 | filter_term = self.request.GET.get('filter') | |||
|
306 | c.user_log = UserModel().get_user_log(c.user, filter_term) | |||
|
307 | ||||
|
308 | def url_generator(**kw): | |||
|
309 | if filter_term: | |||
|
310 | kw['filter'] = filter_term | |||
|
311 | return self.request.current_route_path(_query=kw) | |||
|
312 | ||||
|
313 | c.user_log = Page(c.user_log, page=p, items_per_page=10, | |||
|
314 | url=url_generator) | |||
|
315 | c.filter_term = filter_term | |||
|
316 | return self._get_template_context(c) | |||
|
317 |
@@ -28,101 +28,17 b' import logging' | |||||
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 | from whoosh.qparser.default import QueryParser, query |
|
|||
32 | from whoosh.qparser.dateparse import DateParserPlugin |
|
|||
33 | from whoosh.fields import (TEXT, Schema, DATETIME) |
|
|||
34 | from sqlalchemy.sql.expression import or_, and_, func |
|
|||
35 |
|
31 | |||
36 | 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 | |||
37 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator |
|
34 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator | |
38 | from rhodecode.lib.base import BaseController, render |
|
35 | from rhodecode.lib.base import BaseController, render | |
39 |
from rhodecode.lib.utils2 import safe_int |
|
36 | from rhodecode.lib.utils2 import safe_int | |
40 | from rhodecode.lib.helpers import Page |
|
37 | from rhodecode.lib.helpers import Page | |
41 |
|
38 | |||
42 |
|
39 | |||
43 | log = logging.getLogger(__name__) |
|
40 | log = logging.getLogger(__name__) | |
44 |
|
41 | |||
45 | # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh |
|
|||
46 | # querylang to build sql queries and filter journals |
|
|||
47 | JOURNAL_SCHEMA = Schema( |
|
|||
48 | username=TEXT(), |
|
|||
49 | date=DATETIME(), |
|
|||
50 | action=TEXT(), |
|
|||
51 | repository=TEXT(), |
|
|||
52 | ip=TEXT(), |
|
|||
53 | ) |
|
|||
54 |
|
||||
55 |
|
||||
56 | def _journal_filter(user_log, search_term): |
|
|||
57 | """ |
|
|||
58 | Filters sqlalchemy user_log based on search_term with whoosh Query language |
|
|||
59 | http://packages.python.org/Whoosh/querylang.html |
|
|||
60 |
|
||||
61 | :param user_log: |
|
|||
62 | :param search_term: |
|
|||
63 | """ |
|
|||
64 | log.debug('Initial search term: %r' % search_term) |
|
|||
65 | qry = None |
|
|||
66 | if search_term: |
|
|||
67 | qp = QueryParser('repository', schema=JOURNAL_SCHEMA) |
|
|||
68 | qp.add_plugin(DateParserPlugin()) |
|
|||
69 | qry = qp.parse(unicode(search_term)) |
|
|||
70 | log.debug('Filtering using parsed query %r' % qry) |
|
|||
71 |
|
||||
72 | def wildcard_handler(col, wc_term): |
|
|||
73 | if wc_term.startswith('*') and not wc_term.endswith('*'): |
|
|||
74 | # postfix == endswith |
|
|||
75 | wc_term = remove_prefix(wc_term, prefix='*') |
|
|||
76 | return func.lower(col).endswith(wc_term) |
|
|||
77 | elif wc_term.startswith('*') and wc_term.endswith('*'): |
|
|||
78 | # wildcard == ilike |
|
|||
79 | wc_term = remove_prefix(wc_term, prefix='*') |
|
|||
80 | wc_term = remove_suffix(wc_term, suffix='*') |
|
|||
81 | return func.lower(col).contains(wc_term) |
|
|||
82 |
|
||||
83 | def get_filterion(field, val, term): |
|
|||
84 |
|
||||
85 | if field == 'repository': |
|
|||
86 | field = getattr(UserLog, 'repository_name') |
|
|||
87 | elif field == 'ip': |
|
|||
88 | field = getattr(UserLog, 'user_ip') |
|
|||
89 | elif field == 'date': |
|
|||
90 | field = getattr(UserLog, 'action_date') |
|
|||
91 | elif field == 'username': |
|
|||
92 | field = getattr(UserLog, 'username') |
|
|||
93 | else: |
|
|||
94 | field = getattr(UserLog, field) |
|
|||
95 | log.debug('filter field: %s val=>%s' % (field, val)) |
|
|||
96 |
|
||||
97 | # sql filtering |
|
|||
98 | if isinstance(term, query.Wildcard): |
|
|||
99 | return wildcard_handler(field, val) |
|
|||
100 | elif isinstance(term, query.Prefix): |
|
|||
101 | return func.lower(field).startswith(func.lower(val)) |
|
|||
102 | elif isinstance(term, query.DateRange): |
|
|||
103 | return and_(field >= val[0], field <= val[1]) |
|
|||
104 | return func.lower(field) == func.lower(val) |
|
|||
105 |
|
||||
106 | if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard, |
|
|||
107 | query.DateRange)): |
|
|||
108 | if not isinstance(qry, query.And): |
|
|||
109 | qry = [qry] |
|
|||
110 | for term in qry: |
|
|||
111 | field = term.fieldname |
|
|||
112 | val = (term.text if not isinstance(term, query.DateRange) |
|
|||
113 | else [term.startdate, term.enddate]) |
|
|||
114 | user_log = user_log.filter(get_filterion(field, val, term)) |
|
|||
115 | elif isinstance(qry, query.Or): |
|
|||
116 | filters = [] |
|
|||
117 | for term in qry: |
|
|||
118 | field = term.fieldname |
|
|||
119 | val = (term.text if not isinstance(term, query.DateRange) |
|
|||
120 | else [term.startdate, term.enddate]) |
|
|||
121 | filters.append(get_filterion(field, val, term)) |
|
|||
122 | user_log = user_log.filter(or_(*filters)) |
|
|||
123 |
|
||||
124 | return user_log |
|
|||
125 |
|
||||
126 |
|
42 | |||
127 | class AdminController(BaseController): |
|
43 | class AdminController(BaseController): | |
128 |
|
44 | |||
@@ -139,7 +55,7 b' class AdminController(BaseController):' | |||||
139 | # FILTERING |
|
55 | # FILTERING | |
140 | c.search_term = request.GET.get('filter') |
|
56 | c.search_term = request.GET.get('filter') | |
141 | try: |
|
57 | try: | |
142 |
users_log = |
|
58 | users_log = user_log_filter(users_log, c.search_term) | |
143 | except Exception: |
|
59 | except Exception: | |
144 | # we want this to crash for now |
|
60 | # we want this to crash for now | |
145 | raise |
|
61 | raise |
@@ -34,7 +34,7 b' from webob.exc import HTTPBadRequest' | |||||
34 | from pylons import request, tmpl_context as c, response, url |
|
34 | from pylons import request, tmpl_context as c, response, url | |
35 | from pylons.i18n.translation import _ |
|
35 | from pylons.i18n.translation import _ | |
36 |
|
36 | |||
37 |
from rhodecode.controllers.admin.admin import |
|
37 | from rhodecode.controllers.admin.admin import user_log_filter | |
38 | from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys |
|
38 | from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys | |
39 | from rhodecode.model.meta import Session |
|
39 | from rhodecode.model.meta import Session | |
40 | import rhodecode.lib.helpers as h |
|
40 | import rhodecode.lib.helpers as h | |
@@ -89,7 +89,7 b' class JournalController(BaseController):' | |||||
89 | .options(joinedload(UserLog.repository)) |
|
89 | .options(joinedload(UserLog.repository)) | |
90 | #filter |
|
90 | #filter | |
91 | try: |
|
91 | try: | |
92 |
journal = |
|
92 | journal = user_log_filter(journal, c.search_term) | |
93 | except Exception: |
|
93 | except Exception: | |
94 | # we want this to crash for now |
|
94 | # we want this to crash for now | |
95 | raise |
|
95 | raise |
@@ -33,6 +33,7 b' from sqlalchemy.exc import DatabaseError' | |||||
33 | from sqlalchemy.sql.expression import true, false |
|
33 | from sqlalchemy.sql.expression import true, false | |
34 |
|
34 | |||
35 | from rhodecode import events |
|
35 | from rhodecode import events | |
|
36 | from rhodecode.lib.user_log_filter import user_log_filter | |||
36 | from rhodecode.lib.utils2 import ( |
|
37 | from rhodecode.lib.utils2 import ( | |
37 | safe_unicode, get_current_rhodecode_user, action_logger_generic, |
|
38 | safe_unicode, get_current_rhodecode_user, action_logger_generic, | |
38 | AttributeDict, str2bool) |
|
39 | AttributeDict, str2bool) | |
@@ -40,7 +41,7 b' from rhodecode.lib.caching_query import ' | |||||
40 | from rhodecode.model import BaseModel |
|
41 | from rhodecode.model import BaseModel | |
41 | from rhodecode.model.auth_token import AuthTokenModel |
|
42 | from rhodecode.model.auth_token import AuthTokenModel | |
42 | from rhodecode.model.db import ( |
|
43 | from rhodecode.model.db import ( | |
43 | User, UserToPerm, UserEmailMap, UserIpMap) |
|
44 | or_, joinedload, User, UserToPerm, UserEmailMap, UserIpMap, UserLog) | |
44 | from rhodecode.lib.exceptions import ( |
|
45 | from rhodecode.lib.exceptions import ( | |
45 | DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException, |
|
46 | DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException, | |
46 | UserOwnsUserGroupsException, NotAllowedToCreateUserError) |
|
47 | UserOwnsUserGroupsException, NotAllowedToCreateUserError) | |
@@ -847,3 +848,14 b' class UserModel(BaseModel):' | |||||
847 | Session().commit() |
|
848 | Session().commit() | |
848 |
|
849 | |||
849 | return |
|
850 | return | |
|
851 | ||||
|
852 | def get_user_log(self, user, filter_term): | |||
|
853 | user_log = UserLog.query()\ | |||
|
854 | .filter(or_(UserLog.user_id == user.user_id, | |||
|
855 | UserLog.username == user.username))\ | |||
|
856 | .options(joinedload(UserLog.user))\ | |||
|
857 | .options(joinedload(UserLog.repository))\ | |||
|
858 | .order_by(UserLog.action_date.desc()) | |||
|
859 | ||||
|
860 | user_log = user_log_filter(user_log, filter_term) | |||
|
861 | return user_log |
@@ -37,11 +37,8 b'' | |||||
37 | <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li> |
|
37 | <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li> | |
38 | <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li> |
|
38 | <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li> | |
39 | <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li> |
|
39 | <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li> | |
40 |
|
40 | <li class="${'active' if c.active=='groups' else ''}"><a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a></li> | ||
41 | <li class="${'active' if c.active=='groups' else ''}"> |
|
41 | <li class="${'active' if c.active=='audit' else ''}"><a href="${h.route_path('edit_user_audit_logs', user_id=c.user.user_id)}">${_('User audit')}</a></li> | |
42 | <a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a> |
|
|||
43 | </li> |
|
|||
44 |
|
||||
45 | </ul> |
|
42 | </ul> | |
46 | </div> |
|
43 | </div> | |
47 |
|
44 |
General Comments 0
You need to be logged in to leave comments.
Login now