##// END OF EJS Templates
audit-logs: introduced new view to replace admin journal....
marcink -
r1758:98d57cd2 default
parent child Browse files
Show More
@@ -0,0 +1,187 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 os
22 import csv
23 import datetime
24
25 import pytest
26
27 from rhodecode.tests import *
28 from rhodecode.tests.fixture import FIXTURES
29 from rhodecode.model.db import UserLog
30 from rhodecode.model.meta import Session
31 from rhodecode.lib.utils2 import safe_unicode
32
33
34 def route_path(name, params=None, **kwargs):
35 import urllib
36 from rhodecode.apps._base import ADMIN_PREFIX
37
38 base_url = {
39 'admin_home': ADMIN_PREFIX,
40 'admin_audit_logs': ADMIN_PREFIX + '/audit_logs',
41
42 }[name].format(**kwargs)
43
44 if params:
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 return base_url
47
48
49 class TestAdminController(TestController):
50
51 @pytest.fixture(scope='class', autouse=True)
52 def prepare(self, request, pylonsapp):
53 UserLog.query().delete()
54 Session().commit()
55
56 def strptime(val):
57 fmt = '%Y-%m-%d %H:%M:%S'
58 if '.' not in val:
59 return datetime.datetime.strptime(val, fmt)
60
61 nofrag, frag = val.split(".")
62 date = datetime.datetime.strptime(nofrag, fmt)
63
64 frag = frag[:6] # truncate to microseconds
65 frag += (6 - len(frag)) * '0' # add 0s
66 return date.replace(microsecond=int(frag))
67
68 with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
69 for row in csv.DictReader(f):
70 ul = UserLog()
71 for k, v in row.iteritems():
72 v = safe_unicode(v)
73 if k == 'action_date':
74 v = strptime(v)
75 if k in ['user_id', 'repository_id']:
76 # nullable due to FK problems
77 v = None
78 setattr(ul, k, v)
79 Session().add(ul)
80 Session().commit()
81
82 @request.addfinalizer
83 def cleanup():
84 UserLog.query().delete()
85 Session().commit()
86
87 def test_index(self):
88 self.log_user()
89 response = self.app.get(route_path('admin_audit_logs'))
90 response.mustcontain('Admin audit logs')
91
92 def test_filter_all_entries(self):
93 self.log_user()
94 response = self.app.get(route_path('admin_audit_logs'))
95 all_count = UserLog.query().count()
96 response.mustcontain('%s entries' % all_count)
97
98 def test_filter_journal_filter_exact_match_on_repository(self):
99 self.log_user()
100 response = self.app.get(route_path('admin_audit_logs',
101 params=dict(filter='repository:rhodecode')))
102 response.mustcontain('3 entries')
103
104 def test_filter_journal_filter_exact_match_on_repository_CamelCase(self):
105 self.log_user()
106 response = self.app.get(route_path('admin_audit_logs',
107 params=dict(filter='repository:RhodeCode')))
108 response.mustcontain('3 entries')
109
110 def test_filter_journal_filter_wildcard_on_repository(self):
111 self.log_user()
112 response = self.app.get(route_path('admin_audit_logs',
113 params=dict(filter='repository:*test*')))
114 response.mustcontain('862 entries')
115
116 def test_filter_journal_filter_prefix_on_repository(self):
117 self.log_user()
118 response = self.app.get(route_path('admin_audit_logs',
119 params=dict(filter='repository:test*')))
120 response.mustcontain('257 entries')
121
122 def test_filter_journal_filter_prefix_on_repository_CamelCase(self):
123 self.log_user()
124 response = self.app.get(route_path('admin_audit_logs',
125 params=dict(filter='repository:Test*')))
126 response.mustcontain('257 entries')
127
128 def test_filter_journal_filter_prefix_on_repository_and_user(self):
129 self.log_user()
130 response = self.app.get(route_path('admin_audit_logs',
131 params=dict(filter='repository:test* AND username:demo')))
132 response.mustcontain('130 entries')
133
134 def test_filter_journal_filter_prefix_on_repository_or_target_repo(self):
135 self.log_user()
136 response = self.app.get(route_path('admin_audit_logs',
137 params=dict(filter='repository:test* OR repository:rhodecode')))
138 response.mustcontain('260 entries') # 257 + 3
139
140 def test_filter_journal_filter_exact_match_on_username(self):
141 self.log_user()
142 response = self.app.get(route_path('admin_audit_logs',
143 params=dict(filter='username:demo')))
144 response.mustcontain('1087 entries')
145
146 def test_filter_journal_filter_exact_match_on_username_camelCase(self):
147 self.log_user()
148 response = self.app.get(route_path('admin_audit_logs',
149 params=dict(filter='username:DemO')))
150 response.mustcontain('1087 entries')
151
152 def test_filter_journal_filter_wildcard_on_username(self):
153 self.log_user()
154 response = self.app.get(route_path('admin_audit_logs',
155 params=dict(filter='username:*test*')))
156 entries_count = UserLog.query().filter(UserLog.username.ilike('%test%')).count()
157 response.mustcontain('{} entries'.format(entries_count))
158
159 def test_filter_journal_filter_prefix_on_username(self):
160 self.log_user()
161 response = self.app.get(route_path('admin_audit_logs',
162 params=dict(filter='username:demo*')))
163 response.mustcontain('1101 entries')
164
165 def test_filter_journal_filter_prefix_on_user_or_other_user(self):
166 self.log_user()
167 response = self.app.get(route_path('admin_audit_logs',
168 params=dict(filter='username:demo OR username:volcan')))
169 response.mustcontain('1095 entries') # 1087 + 8
170
171 def test_filter_journal_filter_wildcard_on_action(self):
172 self.log_user()
173 response = self.app.get(route_path('admin_audit_logs',
174 params=dict(filter='action:*pull_request*')))
175 response.mustcontain('187 entries')
176
177 def test_filter_journal_filter_on_date(self):
178 self.log_user()
179 response = self.app.get(route_path('admin_audit_logs',
180 params=dict(filter='date:20121010')))
181 response.mustcontain('47 entries')
182
183 def test_filter_journal_filter_on_date_2(self):
184 self.log_user()
185 response = self.app.get(route_path('admin_audit_logs',
186 params=dict(filter='date:20121020')))
187 response.mustcontain('17 entries')
@@ -0,0 +1,82 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 pytest
22
23 from rhodecode.tests import TestController
24 from rhodecode.tests.fixture import Fixture
25
26 fixture = Fixture()
27
28
29 def route_path(name, params=None, **kwargs):
30 import urllib
31 from rhodecode.apps._base import ADMIN_PREFIX
32
33 base_url = {
34 'admin_home': ADMIN_PREFIX,
35 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
36 'pull_requests_global': ADMIN_PREFIX + '/pull-request/{pull_request_id}',
37 'pull_requests_global_0': ADMIN_PREFIX + '/pull_requests/{pull_request_id}',
38 'pull_requests_global_1': ADMIN_PREFIX + '/pull-requests/{pull_request_id}',
39
40 }[name].format(**kwargs)
41
42 if params:
43 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
44 return base_url
45
46
47 class TestAdminMainView(TestController):
48
49 def test_redirect_admin_home(self):
50 self.log_user()
51 response = self.app.get(route_path('admin_home'), status=302)
52 assert response.location.endswith('/audit_logs')
53
54 def test_redirect_pull_request_view(self, view):
55 self.log_user()
56 self.app.get(
57 route_path(view, pull_request_id='xxxx'),
58 status=404)
59
60 @pytest.mark.backends("git", "hg")
61 @pytest.mark.parametrize('view', [
62 'pull_requests_global',
63 'pull_requests_global_0',
64 'pull_requests_global_1',
65 ])
66 def test_redirect_pull_request_view(self, view, pr_util):
67 self.log_user()
68 pull_request = pr_util.create_pull_request()
69 pull_request_id = pull_request.pull_request_id
70
71 response = self.app.get(
72 route_path(view, pull_request_id=pull_request_id),
73 status=302)
74 assert response.location.endswith(
75 'pull-request/{}'.format(pull_request_id))
76
77 repo_name = pull_request.target_repo.repo_name
78 redirect_url = route_path(
79 'pullrequest_show', repo_name=repo_name,
80 pull_request_id=pull_request.pull_request_id)
81
82 assert redirect_url in response.location
@@ -0,0 +1,73 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-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 pyramid.view import view_config
24 from sqlalchemy.orm import joinedload
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.model.db import UserLog
28 from rhodecode.lib.user_log_filter import user_log_filter
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib.helpers import Page
32
33 log = logging.getLogger(__name__)
34
35
36 class AdminAuditLogsView(BaseAppView):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
39 self._register_global_c(c)
40 return c
41
42 @LoginRequired()
43 @HasPermissionAllDecorator('hg.admin')
44 @view_config(
45 route_name='admin_audit_logs', request_method='GET',
46 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
47 def admin_audit_logs(self):
48 c = self.load_default_context()
49
50 users_log = UserLog.query()\
51 .options(joinedload(UserLog.user))\
52 .options(joinedload(UserLog.repository))
53
54 # FILTERING
55 c.search_term = self.request.GET.get('filter')
56 try:
57 users_log = user_log_filter(users_log, c.search_term)
58 except Exception:
59 # we want this to crash for now
60 raise
61
62 users_log = users_log.order_by(UserLog.action_date.desc())
63
64 p = safe_int(self.request.GET.get('page', 1), 1)
65
66 def url_generator(**kw):
67 if c.search_term:
68 kw['filter'] = c.search_term
69 return self.request.current_route_path(_query=kw)
70
71 c.audit_logs = Page(users_log, page=p, items_per_page=10,
72 url=url_generator)
73 return self._get_template_context(c)
@@ -0,0 +1,63 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-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
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
30 from rhodecode.model.db import PullRequest
31
32
33 log = logging.getLogger(__name__)
34
35
36 class AdminMainView(BaseAppView):
37
38 @LoginRequired()
39 @HasPermissionAllDecorator('hg.admin')
40 @view_config(
41 route_name='admin_home', request_method='GET')
42 def admin_main(self):
43 # redirect _admin to audit logs...
44 raise HTTPFound(h.route_path('admin_audit_logs'))
45
46 @LoginRequired()
47 @view_config(route_name='pull_requests_global_0', request_method='GET')
48 @view_config(route_name='pull_requests_global_1', request_method='GET')
49 @view_config(route_name='pull_requests_global', request_method='GET')
50 def pull_requests(self):
51 """
52 Global redirect for Pull Requests
53
54 :param pull_request_id: id of pull requests in the system
55 """
56
57 pull_request_id = self.request.matchdict.get('pull_request_id')
58 pull_request = PullRequest.get_or_404(pull_request_id, pyramid_exc=True)
59 repo_name = pull_request.target_repo.repo_name
60
61 raise HTTPFound(
62 h.route_path('pullrequest_show', repo_name=repo_name,
63 pull_request_id=pull_request_id))
@@ -1,99 +1,115 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 23 from rhodecode.config.routing import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def admin_routes(config):
28 28 """
29 29 Admin prefixed routes
30 30 """
31 31
32 32 config.add_route(
33 name='admin_audit_logs',
34 pattern='/audit_logs')
35
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:[0-9]+}')
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:[0-9]+}')
42 config.add_route(
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:[0-9]+}')
45
46 config.add_route(
33 47 name='admin_settings_open_source',
34 48 pattern='/settings/open_source')
35 49 config.add_route(
36 50 name='admin_settings_vcs_svn_generate_cfg',
37 51 pattern='/settings/vcs/svn_generate_cfg')
38 52
39 53 config.add_route(
40 54 name='admin_settings_system',
41 55 pattern='/settings/system')
42 56 config.add_route(
43 57 name='admin_settings_system_update',
44 58 pattern='/settings/system/updates')
45 59
46 60 config.add_route(
47 61 name='admin_settings_sessions',
48 62 pattern='/settings/sessions')
49 63 config.add_route(
50 64 name='admin_settings_sessions_cleanup',
51 65 pattern='/settings/sessions/cleanup')
52 66
53 67 # users admin
54 68 config.add_route(
55 69 name='users',
56 70 pattern='/users')
57 71
58 72 config.add_route(
59 73 name='users_data',
60 74 pattern='/users_data')
61 75
62 76 # user auth tokens
63 77 config.add_route(
64 78 name='edit_user_auth_tokens',
65 79 pattern='/users/{user_id:\d+}/edit/auth_tokens')
66 80 config.add_route(
67 81 name='edit_user_auth_tokens_add',
68 82 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
69 83 config.add_route(
70 84 name='edit_user_auth_tokens_delete',
71 85 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
72 86
73 87 # user groups management
74 88 config.add_route(
75 89 name='edit_user_groups_management',
76 90 pattern='/users/{user_id:\d+}/edit/groups_management')
77 91
78 92 config.add_route(
79 93 name='edit_user_groups_management_updates',
80 94 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
81 95
82 96 # user audit logs
83 97 config.add_route(
84 98 name='edit_user_audit_logs',
85 99 pattern='/users/{user_id:\d+}/edit/audit')
86 100
87 101
88 102 def includeme(config):
89 103 settings = config.get_settings()
90 104
91 105 # Create admin navigation registry and add it to the pyramid registry.
92 106 labs_active = str2bool(settings.get('labs_settings_active', False))
93 107 navigation_registry = NavigationRegistry(labs_active=labs_active)
94 108 config.registry.registerUtility(navigation_registry)
95 109
110 # main admin routes
111 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
96 112 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
97 113
98 114 # Scan module for configuration decorators.
99 115 config.scan()
@@ -1,109 +1,115 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 def includeme(config):
23 23
24 24 # Summary
25 25 config.add_route(
26 26 name='repo_summary_explicit',
27 27 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
28 28
29 29 # Tags
30 30 config.add_route(
31 31 name='tags_home',
32 32 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
33 33
34 34 # Branches
35 35 config.add_route(
36 36 name='branches_home',
37 37 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
38 38
39 39 # Bookmarks
40 40 config.add_route(
41 41 name='bookmarks_home',
42 42 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
43 43
44 # Pull Requests
45 config.add_route(
46 name='pullrequest_show',
47 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}',
48 repo_route=True)
49
44 50 # Settings
45 51 config.add_route(
46 52 name='edit_repo',
47 53 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
48 54
49 55 # Settings advanced
50 56 config.add_route(
51 57 name='edit_repo_advanced',
52 58 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
53 59 config.add_route(
54 60 name='edit_repo_advanced_delete',
55 61 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
56 62 config.add_route(
57 63 name='edit_repo_advanced_locking',
58 64 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
59 65 config.add_route(
60 66 name='edit_repo_advanced_journal',
61 67 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
62 68 config.add_route(
63 69 name='edit_repo_advanced_fork',
64 70 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
65 71
66 72 # Caches
67 73 config.add_route(
68 74 name='edit_repo_caches',
69 75 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
70 76
71 77 # Permissions
72 78 config.add_route(
73 79 name='edit_repo_perms',
74 80 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
75 81
76 82 # Repo Review Rules
77 83 config.add_route(
78 84 name='repo_reviewers',
79 85 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
80 86
81 87 # Maintenance
82 88 config.add_route(
83 89 name='repo_maintenance',
84 90 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
85 91
86 92 config.add_route(
87 93 name='repo_maintenance_execute',
88 94 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
89 95
90 96 # Strip
91 97 config.add_route(
92 98 name='strip',
93 99 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
94 100
95 101 config.add_route(
96 102 name='strip_check',
97 103 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
98 104
99 105 config.add_route(
100 106 name='strip_execute',
101 107 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
102 108
103 109 # NOTE(marcink): needs to be at the end for catch-all
104 110 # config.add_route(
105 111 # name='repo_summary',
106 112 # pattern='/{repo_name:.*?[^/]}', repo_route=True)
107 113
108 114 # Scan module for configuration decorators.
109 115 config.scan()
@@ -1,1053 +1,1037 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_requirements(route_path, requirements):
55 55 """
56 56 Adds regex requirements to pyramid routes using a mapping dict
57 57
58 58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 59 '/{action}/{id:\d+}'
60 60
61 61 """
62 62 for key, regex in requirements.items():
63 63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 64 return route_path
65 65
66 66
67 67 class JSRoutesMapper(Mapper):
68 68 """
69 69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 70 """
71 71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 73 def __init__(self, *args, **kw):
74 74 super(JSRoutesMapper, self).__init__(*args, **kw)
75 75 self._jsroutes = []
76 76
77 77 def connect(self, *args, **kw):
78 78 """
79 79 Wrapper for connect to take an extra argument jsroute=True
80 80
81 81 :param jsroute: boolean, if True will add the route to the pyroutes list
82 82 """
83 83 if kw.pop('jsroute', False):
84 84 if not self._named_route_regex.match(args[0]):
85 85 raise Exception('only named routes can be added to pyroutes')
86 86 self._jsroutes.append(args[0])
87 87
88 88 super(JSRoutesMapper, self).connect(*args, **kw)
89 89
90 90 def _extract_route_information(self, route):
91 91 """
92 92 Convert a route into tuple(name, path, args), eg:
93 93 ('show_user', '/profile/%(username)s', ['username'])
94 94 """
95 95 routepath = route.routepath
96 96 def replace(matchobj):
97 97 if matchobj.group(1):
98 98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 99 else:
100 100 return "%%(%s)s" % matchobj.group(2)
101 101
102 102 routepath = self._argument_prog.sub(replace, routepath)
103 103 return (
104 104 route.name,
105 105 routepath,
106 106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 107 for arg in self._argument_prog.findall(route.routepath)]
108 108 )
109 109
110 110 def jsroutes(self):
111 111 """
112 112 Return a list of pyroutes.js compatible routes
113 113 """
114 114 for route_name in self._jsroutes:
115 115 yield self._extract_route_information(self._routenames[route_name])
116 116
117 117
118 118 def make_map(config):
119 119 """Create, configure and return the routes Mapper"""
120 120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 121 always_scan=config['debug'])
122 122 rmap.minimization = False
123 123 rmap.explicit = False
124 124
125 125 from rhodecode.lib.utils2 import str2bool
126 126 from rhodecode.model import repo, repo_group
127 127
128 128 def check_repo(environ, match_dict):
129 129 """
130 130 check for valid repository for proper 404 handling
131 131
132 132 :param environ:
133 133 :param match_dict:
134 134 """
135 135 repo_name = match_dict.get('repo_name')
136 136
137 137 if match_dict.get('f_path'):
138 138 # fix for multiple initial slashes that causes errors
139 139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 140 repo_model = repo.RepoModel()
141 141 by_name_match = repo_model.get_by_repo_name(repo_name)
142 142 # if we match quickly from database, short circuit the operation,
143 143 # and validate repo based on the type.
144 144 if by_name_match:
145 145 return True
146 146
147 147 by_id_match = repo_model.get_repo_by_id(repo_name)
148 148 if by_id_match:
149 149 repo_name = by_id_match.repo_name
150 150 match_dict['repo_name'] = repo_name
151 151 return True
152 152
153 153 return False
154 154
155 155 def check_group(environ, match_dict):
156 156 """
157 157 check for valid repository group path for proper 404 handling
158 158
159 159 :param environ:
160 160 :param match_dict:
161 161 """
162 162 repo_group_name = match_dict.get('group_name')
163 163 repo_group_model = repo_group.RepoGroupModel()
164 164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 165 if by_name_match:
166 166 return True
167 167
168 168 return False
169 169
170 170 def check_user_group(environ, match_dict):
171 171 """
172 172 check for valid user group for proper 404 handling
173 173
174 174 :param environ:
175 175 :param match_dict:
176 176 """
177 177 return True
178 178
179 179 def check_int(environ, match_dict):
180 180 return match_dict.get('id').isdigit()
181 181
182 182
183 183 #==========================================================================
184 184 # CUSTOM ROUTES HERE
185 185 #==========================================================================
186 186
187 187 # MAIN PAGE
188 188 rmap.connect('home', '/', controller='home', action='index')
189 189
190 190 # ping and pylons error test
191 191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
192 192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
193 193
194 194 # ADMIN REPOSITORY ROUTES
195 195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
196 196 controller='admin/repos') as m:
197 197 m.connect('repos', '/repos',
198 198 action='create', conditions={'method': ['POST']})
199 199 m.connect('repos', '/repos',
200 200 action='index', conditions={'method': ['GET']})
201 201 m.connect('new_repo', '/create_repository', jsroute=True,
202 202 action='create_repository', conditions={'method': ['GET']})
203 203 m.connect('delete_repo', '/repos/{repo_name}',
204 204 action='delete', conditions={'method': ['DELETE']},
205 205 requirements=URL_NAME_REQUIREMENTS)
206 206 m.connect('repo', '/repos/{repo_name}',
207 207 action='show', conditions={'method': ['GET'],
208 208 'function': check_repo},
209 209 requirements=URL_NAME_REQUIREMENTS)
210 210
211 211 # ADMIN REPOSITORY GROUPS ROUTES
212 212 with rmap.submapper(path_prefix=ADMIN_PREFIX,
213 213 controller='admin/repo_groups') as m:
214 214 m.connect('repo_groups', '/repo_groups',
215 215 action='create', conditions={'method': ['POST']})
216 216 m.connect('repo_groups', '/repo_groups',
217 217 action='index', conditions={'method': ['GET']})
218 218 m.connect('new_repo_group', '/repo_groups/new',
219 219 action='new', conditions={'method': ['GET']})
220 220 m.connect('update_repo_group', '/repo_groups/{group_name}',
221 221 action='update', conditions={'method': ['PUT'],
222 222 'function': check_group},
223 223 requirements=URL_NAME_REQUIREMENTS)
224 224
225 225 # EXTRAS REPO GROUP ROUTES
226 226 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
227 227 action='edit',
228 228 conditions={'method': ['GET'], 'function': check_group},
229 229 requirements=URL_NAME_REQUIREMENTS)
230 230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
231 231 action='edit',
232 232 conditions={'method': ['PUT'], 'function': check_group},
233 233 requirements=URL_NAME_REQUIREMENTS)
234 234
235 235 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
236 236 action='edit_repo_group_advanced',
237 237 conditions={'method': ['GET'], 'function': check_group},
238 238 requirements=URL_NAME_REQUIREMENTS)
239 239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
240 240 action='edit_repo_group_advanced',
241 241 conditions={'method': ['PUT'], 'function': check_group},
242 242 requirements=URL_NAME_REQUIREMENTS)
243 243
244 244 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
245 245 action='edit_repo_group_perms',
246 246 conditions={'method': ['GET'], 'function': check_group},
247 247 requirements=URL_NAME_REQUIREMENTS)
248 248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
249 249 action='update_perms',
250 250 conditions={'method': ['PUT'], 'function': check_group},
251 251 requirements=URL_NAME_REQUIREMENTS)
252 252
253 253 m.connect('delete_repo_group', '/repo_groups/{group_name}',
254 254 action='delete', conditions={'method': ['DELETE'],
255 255 'function': check_group},
256 256 requirements=URL_NAME_REQUIREMENTS)
257 257
258 258 # ADMIN USER ROUTES
259 259 with rmap.submapper(path_prefix=ADMIN_PREFIX,
260 260 controller='admin/users') as m:
261 261 m.connect('users', '/users',
262 262 action='create', conditions={'method': ['POST']})
263 263 m.connect('new_user', '/users/new',
264 264 action='new', conditions={'method': ['GET']})
265 265 m.connect('update_user', '/users/{user_id}',
266 266 action='update', conditions={'method': ['PUT']})
267 267 m.connect('delete_user', '/users/{user_id}',
268 268 action='delete', conditions={'method': ['DELETE']})
269 269 m.connect('edit_user', '/users/{user_id}/edit',
270 270 action='edit', conditions={'method': ['GET']}, jsroute=True)
271 271 m.connect('user', '/users/{user_id}',
272 272 action='show', conditions={'method': ['GET']})
273 273 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
274 274 action='reset_password', conditions={'method': ['POST']})
275 275 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
276 276 action='create_personal_repo_group', conditions={'method': ['POST']})
277 277
278 278 # EXTRAS USER ROUTES
279 279 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
280 280 action='edit_advanced', conditions={'method': ['GET']})
281 281 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
282 282 action='update_advanced', conditions={'method': ['PUT']})
283 283
284 284 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
285 285 action='edit_global_perms', conditions={'method': ['GET']})
286 286 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
287 287 action='update_global_perms', conditions={'method': ['PUT']})
288 288
289 289 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
290 290 action='edit_perms_summary', conditions={'method': ['GET']})
291 291
292 292 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
293 293 action='edit_emails', conditions={'method': ['GET']})
294 294 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
295 295 action='add_email', conditions={'method': ['PUT']})
296 296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
297 297 action='delete_email', conditions={'method': ['DELETE']})
298 298
299 299 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
300 300 action='edit_ips', conditions={'method': ['GET']})
301 301 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
302 302 action='add_ip', conditions={'method': ['PUT']})
303 303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
304 304 action='delete_ip', conditions={'method': ['DELETE']})
305 305
306 306 # ADMIN USER GROUPS REST ROUTES
307 307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
308 308 controller='admin/user_groups') as m:
309 309 m.connect('users_groups', '/user_groups',
310 310 action='create', conditions={'method': ['POST']})
311 311 m.connect('users_groups', '/user_groups',
312 312 action='index', conditions={'method': ['GET']})
313 313 m.connect('new_users_group', '/user_groups/new',
314 314 action='new', conditions={'method': ['GET']})
315 315 m.connect('update_users_group', '/user_groups/{user_group_id}',
316 316 action='update', conditions={'method': ['PUT']})
317 317 m.connect('delete_users_group', '/user_groups/{user_group_id}',
318 318 action='delete', conditions={'method': ['DELETE']})
319 319 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
320 320 action='edit', conditions={'method': ['GET']},
321 321 function=check_user_group)
322 322
323 323 # EXTRAS USER GROUP ROUTES
324 324 m.connect('edit_user_group_global_perms',
325 325 '/user_groups/{user_group_id}/edit/global_permissions',
326 326 action='edit_global_perms', conditions={'method': ['GET']})
327 327 m.connect('edit_user_group_global_perms',
328 328 '/user_groups/{user_group_id}/edit/global_permissions',
329 329 action='update_global_perms', conditions={'method': ['PUT']})
330 330 m.connect('edit_user_group_perms_summary',
331 331 '/user_groups/{user_group_id}/edit/permissions_summary',
332 332 action='edit_perms_summary', conditions={'method': ['GET']})
333 333
334 334 m.connect('edit_user_group_perms',
335 335 '/user_groups/{user_group_id}/edit/permissions',
336 336 action='edit_perms', conditions={'method': ['GET']})
337 337 m.connect('edit_user_group_perms',
338 338 '/user_groups/{user_group_id}/edit/permissions',
339 339 action='update_perms', conditions={'method': ['PUT']})
340 340
341 341 m.connect('edit_user_group_advanced',
342 342 '/user_groups/{user_group_id}/edit/advanced',
343 343 action='edit_advanced', conditions={'method': ['GET']})
344 344
345 345 m.connect('edit_user_group_advanced_sync',
346 346 '/user_groups/{user_group_id}/edit/advanced/sync',
347 347 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
348 348
349 349 m.connect('edit_user_group_members',
350 350 '/user_groups/{user_group_id}/edit/members', jsroute=True,
351 351 action='user_group_members', conditions={'method': ['GET']})
352 352
353 353 # ADMIN PERMISSIONS ROUTES
354 354 with rmap.submapper(path_prefix=ADMIN_PREFIX,
355 355 controller='admin/permissions') as m:
356 356 m.connect('admin_permissions_application', '/permissions/application',
357 357 action='permission_application_update', conditions={'method': ['POST']})
358 358 m.connect('admin_permissions_application', '/permissions/application',
359 359 action='permission_application', conditions={'method': ['GET']})
360 360
361 361 m.connect('admin_permissions_global', '/permissions/global',
362 362 action='permission_global_update', conditions={'method': ['POST']})
363 363 m.connect('admin_permissions_global', '/permissions/global',
364 364 action='permission_global', conditions={'method': ['GET']})
365 365
366 366 m.connect('admin_permissions_object', '/permissions/object',
367 367 action='permission_objects_update', conditions={'method': ['POST']})
368 368 m.connect('admin_permissions_object', '/permissions/object',
369 369 action='permission_objects', conditions={'method': ['GET']})
370 370
371 371 m.connect('admin_permissions_ips', '/permissions/ips',
372 372 action='permission_ips', conditions={'method': ['POST']})
373 373 m.connect('admin_permissions_ips', '/permissions/ips',
374 374 action='permission_ips', conditions={'method': ['GET']})
375 375
376 376 m.connect('admin_permissions_overview', '/permissions/overview',
377 377 action='permission_perms', conditions={'method': ['GET']})
378 378
379 379 # ADMIN DEFAULTS REST ROUTES
380 380 with rmap.submapper(path_prefix=ADMIN_PREFIX,
381 381 controller='admin/defaults') as m:
382 382 m.connect('admin_defaults_repositories', '/defaults/repositories',
383 383 action='update_repository_defaults', conditions={'method': ['POST']})
384 384 m.connect('admin_defaults_repositories', '/defaults/repositories',
385 385 action='index', conditions={'method': ['GET']})
386 386
387 387 # ADMIN DEBUG STYLE ROUTES
388 388 if str2bool(config.get('debug_style')):
389 389 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
390 390 controller='debug_style') as m:
391 391 m.connect('debug_style_home', '',
392 392 action='index', conditions={'method': ['GET']})
393 393 m.connect('debug_style_template', '/t/{t_path}',
394 394 action='template', conditions={'method': ['GET']})
395 395
396 396 # ADMIN SETTINGS ROUTES
397 397 with rmap.submapper(path_prefix=ADMIN_PREFIX,
398 398 controller='admin/settings') as m:
399 399
400 400 # default
401 401 m.connect('admin_settings', '/settings',
402 402 action='settings_global_update',
403 403 conditions={'method': ['POST']})
404 404 m.connect('admin_settings', '/settings',
405 405 action='settings_global', conditions={'method': ['GET']})
406 406
407 407 m.connect('admin_settings_vcs', '/settings/vcs',
408 408 action='settings_vcs_update',
409 409 conditions={'method': ['POST']})
410 410 m.connect('admin_settings_vcs', '/settings/vcs',
411 411 action='settings_vcs',
412 412 conditions={'method': ['GET']})
413 413 m.connect('admin_settings_vcs', '/settings/vcs',
414 414 action='delete_svn_pattern',
415 415 conditions={'method': ['DELETE']})
416 416
417 417 m.connect('admin_settings_mapping', '/settings/mapping',
418 418 action='settings_mapping_update',
419 419 conditions={'method': ['POST']})
420 420 m.connect('admin_settings_mapping', '/settings/mapping',
421 421 action='settings_mapping', conditions={'method': ['GET']})
422 422
423 423 m.connect('admin_settings_global', '/settings/global',
424 424 action='settings_global_update',
425 425 conditions={'method': ['POST']})
426 426 m.connect('admin_settings_global', '/settings/global',
427 427 action='settings_global', conditions={'method': ['GET']})
428 428
429 429 m.connect('admin_settings_visual', '/settings/visual',
430 430 action='settings_visual_update',
431 431 conditions={'method': ['POST']})
432 432 m.connect('admin_settings_visual', '/settings/visual',
433 433 action='settings_visual', conditions={'method': ['GET']})
434 434
435 435 m.connect('admin_settings_issuetracker',
436 436 '/settings/issue-tracker', action='settings_issuetracker',
437 437 conditions={'method': ['GET']})
438 438 m.connect('admin_settings_issuetracker_save',
439 439 '/settings/issue-tracker/save',
440 440 action='settings_issuetracker_save',
441 441 conditions={'method': ['POST']})
442 442 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
443 443 action='settings_issuetracker_test',
444 444 conditions={'method': ['POST']})
445 445 m.connect('admin_issuetracker_delete',
446 446 '/settings/issue-tracker/delete',
447 447 action='settings_issuetracker_delete',
448 448 conditions={'method': ['DELETE']})
449 449
450 450 m.connect('admin_settings_email', '/settings/email',
451 451 action='settings_email_update',
452 452 conditions={'method': ['POST']})
453 453 m.connect('admin_settings_email', '/settings/email',
454 454 action='settings_email', conditions={'method': ['GET']})
455 455
456 456 m.connect('admin_settings_hooks', '/settings/hooks',
457 457 action='settings_hooks_update',
458 458 conditions={'method': ['POST', 'DELETE']})
459 459 m.connect('admin_settings_hooks', '/settings/hooks',
460 460 action='settings_hooks', conditions={'method': ['GET']})
461 461
462 462 m.connect('admin_settings_search', '/settings/search',
463 463 action='settings_search', conditions={'method': ['GET']})
464 464
465 465 m.connect('admin_settings_supervisor', '/settings/supervisor',
466 466 action='settings_supervisor', conditions={'method': ['GET']})
467 467 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
468 468 action='settings_supervisor_log', conditions={'method': ['GET']})
469 469
470 470 m.connect('admin_settings_labs', '/settings/labs',
471 471 action='settings_labs_update',
472 472 conditions={'method': ['POST']})
473 473 m.connect('admin_settings_labs', '/settings/labs',
474 474 action='settings_labs', conditions={'method': ['GET']})
475 475
476 476 # ADMIN MY ACCOUNT
477 477 with rmap.submapper(path_prefix=ADMIN_PREFIX,
478 478 controller='admin/my_account') as m:
479 479
480 480 m.connect('my_account_edit', '/my_account/edit',
481 481 action='my_account_edit', conditions={'method': ['GET']})
482 482 m.connect('my_account', '/my_account/update',
483 483 action='my_account_update', conditions={'method': ['POST']})
484 484
485 485 # NOTE(marcink): this needs to be kept for password force flag to be
486 486 # handler, remove after migration to pyramid
487 487 m.connect('my_account_password', '/my_account/password',
488 488 action='my_account_password', conditions={'method': ['GET']})
489 489
490 490 m.connect('my_account_repos', '/my_account/repos',
491 491 action='my_account_repos', conditions={'method': ['GET']})
492 492
493 493 m.connect('my_account_watched', '/my_account/watched',
494 494 action='my_account_watched', conditions={'method': ['GET']})
495 495
496 496 m.connect('my_account_pullrequests', '/my_account/pull_requests',
497 497 action='my_account_pullrequests', conditions={'method': ['GET']})
498 498
499 499 m.connect('my_account_perms', '/my_account/perms',
500 500 action='my_account_perms', conditions={'method': ['GET']})
501 501
502 502 m.connect('my_account_emails', '/my_account/emails',
503 503 action='my_account_emails', conditions={'method': ['GET']})
504 504 m.connect('my_account_emails', '/my_account/emails',
505 505 action='my_account_emails_add', conditions={'method': ['POST']})
506 506 m.connect('my_account_emails', '/my_account/emails',
507 507 action='my_account_emails_delete', conditions={'method': ['DELETE']})
508 508
509 509 m.connect('my_account_notifications', '/my_account/notifications',
510 510 action='my_notifications',
511 511 conditions={'method': ['GET']})
512 512 m.connect('my_account_notifications_toggle_visibility',
513 513 '/my_account/toggle_visibility',
514 514 action='my_notifications_toggle_visibility',
515 515 conditions={'method': ['POST']})
516 516
517 517 # NOTIFICATION REST ROUTES
518 518 with rmap.submapper(path_prefix=ADMIN_PREFIX,
519 519 controller='admin/notifications') as m:
520 520 m.connect('notifications', '/notifications',
521 521 action='index', conditions={'method': ['GET']})
522 522 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
523 523 action='mark_all_read', conditions={'method': ['POST']})
524 524 m.connect('/notifications/{notification_id}',
525 525 action='update', conditions={'method': ['PUT']})
526 526 m.connect('/notifications/{notification_id}',
527 527 action='delete', conditions={'method': ['DELETE']})
528 528 m.connect('notification', '/notifications/{notification_id}',
529 529 action='show', conditions={'method': ['GET']})
530 530
531 531 # ADMIN GIST
532 532 with rmap.submapper(path_prefix=ADMIN_PREFIX,
533 533 controller='admin/gists') as m:
534 534 m.connect('gists', '/gists',
535 535 action='create', conditions={'method': ['POST']})
536 536 m.connect('gists', '/gists', jsroute=True,
537 537 action='index', conditions={'method': ['GET']})
538 538 m.connect('new_gist', '/gists/new', jsroute=True,
539 539 action='new', conditions={'method': ['GET']})
540 540
541 541 m.connect('/gists/{gist_id}',
542 542 action='delete', conditions={'method': ['DELETE']})
543 543 m.connect('edit_gist', '/gists/{gist_id}/edit',
544 544 action='edit_form', conditions={'method': ['GET']})
545 545 m.connect('edit_gist', '/gists/{gist_id}/edit',
546 546 action='edit', conditions={'method': ['POST']})
547 547 m.connect(
548 548 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
549 549 action='check_revision', conditions={'method': ['GET']})
550 550
551 551 m.connect('gist', '/gists/{gist_id}',
552 552 action='show', conditions={'method': ['GET']})
553 553 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
554 554 revision='tip',
555 555 action='show', conditions={'method': ['GET']})
556 556 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
557 557 revision='tip',
558 558 action='show', conditions={'method': ['GET']})
559 559 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
560 560 revision='tip',
561 561 action='show', conditions={'method': ['GET']},
562 562 requirements=URL_NAME_REQUIREMENTS)
563 563
564 # ADMIN MAIN PAGES
565 with rmap.submapper(path_prefix=ADMIN_PREFIX,
566 controller='admin/admin') as m:
567 m.connect('admin_home', '', action='index')
568 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
569 action='add_repo')
570 m.connect(
571 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
572 action='pull_requests')
573 m.connect(
574 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
575 action='pull_requests')
576 m.connect(
577 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
578 action='pull_requests')
579
580 564 # USER JOURNAL
581 565 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
582 566 controller='journal', action='index')
583 567 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
584 568 controller='journal', action='journal_rss')
585 569 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
586 570 controller='journal', action='journal_atom')
587 571
588 572 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
589 573 controller='journal', action='public_journal')
590 574
591 575 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
592 576 controller='journal', action='public_journal_rss')
593 577
594 578 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
595 579 controller='journal', action='public_journal_rss')
596 580
597 581 rmap.connect('public_journal_atom',
598 582 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
599 583 action='public_journal_atom')
600 584
601 585 rmap.connect('public_journal_atom_old',
602 586 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
603 587 action='public_journal_atom')
604 588
605 589 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
606 590 controller='journal', action='toggle_following', jsroute=True,
607 591 conditions={'method': ['POST']})
608 592
609 593 # FEEDS
610 594 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
611 595 controller='feed', action='rss',
612 596 conditions={'function': check_repo},
613 597 requirements=URL_NAME_REQUIREMENTS)
614 598
615 599 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
616 600 controller='feed', action='atom',
617 601 conditions={'function': check_repo},
618 602 requirements=URL_NAME_REQUIREMENTS)
619 603
620 604 #==========================================================================
621 605 # REPOSITORY ROUTES
622 606 #==========================================================================
623 607
624 608 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
625 609 controller='admin/repos', action='repo_creating',
626 610 requirements=URL_NAME_REQUIREMENTS)
627 611 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
628 612 controller='admin/repos', action='repo_check',
629 613 requirements=URL_NAME_REQUIREMENTS)
630 614
631 615 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
632 616 controller='summary', action='repo_stats',
633 617 conditions={'function': check_repo},
634 618 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
635 619
636 620 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
637 621 controller='summary', action='repo_refs_data',
638 622 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
639 623 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
640 624 controller='summary', action='repo_refs_changelog_data',
641 625 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
642 626 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
643 627 controller='summary', action='repo_default_reviewers_data',
644 628 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
645 629
646 630 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
647 631 controller='changeset', revision='tip',
648 632 conditions={'function': check_repo},
649 633 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
650 634 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
651 635 controller='changeset', revision='tip', action='changeset_children',
652 636 conditions={'function': check_repo},
653 637 requirements=URL_NAME_REQUIREMENTS)
654 638 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
655 639 controller='changeset', revision='tip', action='changeset_parents',
656 640 conditions={'function': check_repo},
657 641 requirements=URL_NAME_REQUIREMENTS)
658 642
659 643 # repo edit options
660 644 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
661 645 controller='admin/repos', action='edit_fields',
662 646 conditions={'method': ['GET'], 'function': check_repo},
663 647 requirements=URL_NAME_REQUIREMENTS)
664 648 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
665 649 controller='admin/repos', action='create_repo_field',
666 650 conditions={'method': ['PUT'], 'function': check_repo},
667 651 requirements=URL_NAME_REQUIREMENTS)
668 652 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
669 653 controller='admin/repos', action='delete_repo_field',
670 654 conditions={'method': ['DELETE'], 'function': check_repo},
671 655 requirements=URL_NAME_REQUIREMENTS)
672 656
673 657 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
674 658 controller='admin/repos', action='toggle_locking',
675 659 conditions={'method': ['GET'], 'function': check_repo},
676 660 requirements=URL_NAME_REQUIREMENTS)
677 661
678 662 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
679 663 controller='admin/repos', action='edit_remote_form',
680 664 conditions={'method': ['GET'], 'function': check_repo},
681 665 requirements=URL_NAME_REQUIREMENTS)
682 666 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
683 667 controller='admin/repos', action='edit_remote',
684 668 conditions={'method': ['PUT'], 'function': check_repo},
685 669 requirements=URL_NAME_REQUIREMENTS)
686 670
687 671 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
688 672 controller='admin/repos', action='edit_statistics_form',
689 673 conditions={'method': ['GET'], 'function': check_repo},
690 674 requirements=URL_NAME_REQUIREMENTS)
691 675 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
692 676 controller='admin/repos', action='edit_statistics',
693 677 conditions={'method': ['PUT'], 'function': check_repo},
694 678 requirements=URL_NAME_REQUIREMENTS)
695 679 rmap.connect('repo_settings_issuetracker',
696 680 '/{repo_name}/settings/issue-tracker',
697 681 controller='admin/repos', action='repo_issuetracker',
698 682 conditions={'method': ['GET'], 'function': check_repo},
699 683 requirements=URL_NAME_REQUIREMENTS)
700 684 rmap.connect('repo_issuetracker_test',
701 685 '/{repo_name}/settings/issue-tracker/test',
702 686 controller='admin/repos', action='repo_issuetracker_test',
703 687 conditions={'method': ['POST'], 'function': check_repo},
704 688 requirements=URL_NAME_REQUIREMENTS)
705 689 rmap.connect('repo_issuetracker_delete',
706 690 '/{repo_name}/settings/issue-tracker/delete',
707 691 controller='admin/repos', action='repo_issuetracker_delete',
708 692 conditions={'method': ['DELETE'], 'function': check_repo},
709 693 requirements=URL_NAME_REQUIREMENTS)
710 694 rmap.connect('repo_issuetracker_save',
711 695 '/{repo_name}/settings/issue-tracker/save',
712 696 controller='admin/repos', action='repo_issuetracker_save',
713 697 conditions={'method': ['POST'], 'function': check_repo},
714 698 requirements=URL_NAME_REQUIREMENTS)
715 699 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
716 700 controller='admin/repos', action='repo_settings_vcs_update',
717 701 conditions={'method': ['POST'], 'function': check_repo},
718 702 requirements=URL_NAME_REQUIREMENTS)
719 703 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
720 704 controller='admin/repos', action='repo_settings_vcs',
721 705 conditions={'method': ['GET'], 'function': check_repo},
722 706 requirements=URL_NAME_REQUIREMENTS)
723 707 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
724 708 controller='admin/repos', action='repo_delete_svn_pattern',
725 709 conditions={'method': ['DELETE'], 'function': check_repo},
726 710 requirements=URL_NAME_REQUIREMENTS)
727 711 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
728 712 controller='admin/repos', action='repo_settings_pullrequest',
729 713 conditions={'method': ['GET', 'POST'], 'function': check_repo},
730 714 requirements=URL_NAME_REQUIREMENTS)
731 715
732 716 # still working url for backward compat.
733 717 rmap.connect('raw_changeset_home_depraced',
734 718 '/{repo_name}/raw-changeset/{revision}',
735 719 controller='changeset', action='changeset_raw',
736 720 revision='tip', conditions={'function': check_repo},
737 721 requirements=URL_NAME_REQUIREMENTS)
738 722
739 723 # new URLs
740 724 rmap.connect('changeset_raw_home',
741 725 '/{repo_name}/changeset-diff/{revision}',
742 726 controller='changeset', action='changeset_raw',
743 727 revision='tip', conditions={'function': check_repo},
744 728 requirements=URL_NAME_REQUIREMENTS)
745 729
746 730 rmap.connect('changeset_patch_home',
747 731 '/{repo_name}/changeset-patch/{revision}',
748 732 controller='changeset', action='changeset_patch',
749 733 revision='tip', conditions={'function': check_repo},
750 734 requirements=URL_NAME_REQUIREMENTS)
751 735
752 736 rmap.connect('changeset_download_home',
753 737 '/{repo_name}/changeset-download/{revision}',
754 738 controller='changeset', action='changeset_download',
755 739 revision='tip', conditions={'function': check_repo},
756 740 requirements=URL_NAME_REQUIREMENTS)
757 741
758 742 rmap.connect('changeset_comment',
759 743 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
760 744 controller='changeset', revision='tip', action='comment',
761 745 conditions={'function': check_repo},
762 746 requirements=URL_NAME_REQUIREMENTS)
763 747
764 748 rmap.connect('changeset_comment_preview',
765 749 '/{repo_name}/changeset/comment/preview', jsroute=True,
766 750 controller='changeset', action='preview_comment',
767 751 conditions={'function': check_repo, 'method': ['POST']},
768 752 requirements=URL_NAME_REQUIREMENTS)
769 753
770 754 rmap.connect('changeset_comment_delete',
771 755 '/{repo_name}/changeset/comment/{comment_id}/delete',
772 756 controller='changeset', action='delete_comment',
773 757 conditions={'function': check_repo, 'method': ['DELETE']},
774 758 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
775 759
776 760 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
777 761 controller='changeset', action='changeset_info',
778 762 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
779 763
780 764 rmap.connect('compare_home',
781 765 '/{repo_name}/compare',
782 766 controller='compare', action='index',
783 767 conditions={'function': check_repo},
784 768 requirements=URL_NAME_REQUIREMENTS)
785 769
786 770 rmap.connect('compare_url',
787 771 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
788 772 controller='compare', action='compare',
789 773 conditions={'function': check_repo},
790 774 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
791 775
792 776 rmap.connect('pullrequest_home',
793 777 '/{repo_name}/pull-request/new', controller='pullrequests',
794 778 action='index', conditions={'function': check_repo,
795 779 'method': ['GET']},
796 780 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
797 781
798 782 rmap.connect('pullrequest',
799 783 '/{repo_name}/pull-request/new', controller='pullrequests',
800 784 action='create', conditions={'function': check_repo,
801 785 'method': ['POST']},
802 786 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
803 787
804 788 rmap.connect('pullrequest_repo_refs',
805 789 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
806 790 controller='pullrequests',
807 791 action='get_repo_refs',
808 792 conditions={'function': check_repo, 'method': ['GET']},
809 793 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
810 794
811 795 rmap.connect('pullrequest_repo_destinations',
812 796 '/{repo_name}/pull-request/repo-destinations',
813 797 controller='pullrequests',
814 798 action='get_repo_destinations',
815 799 conditions={'function': check_repo, 'method': ['GET']},
816 800 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
817 801
818 802 rmap.connect('pullrequest_show',
819 803 '/{repo_name}/pull-request/{pull_request_id}',
820 804 controller='pullrequests',
821 805 action='show', conditions={'function': check_repo,
822 806 'method': ['GET']},
823 807 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
824 808
825 809 rmap.connect('pullrequest_update',
826 810 '/{repo_name}/pull-request/{pull_request_id}',
827 811 controller='pullrequests',
828 812 action='update', conditions={'function': check_repo,
829 813 'method': ['PUT']},
830 814 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
831 815
832 816 rmap.connect('pullrequest_merge',
833 817 '/{repo_name}/pull-request/{pull_request_id}',
834 818 controller='pullrequests',
835 819 action='merge', conditions={'function': check_repo,
836 820 'method': ['POST']},
837 821 requirements=URL_NAME_REQUIREMENTS)
838 822
839 823 rmap.connect('pullrequest_delete',
840 824 '/{repo_name}/pull-request/{pull_request_id}',
841 825 controller='pullrequests',
842 826 action='delete', conditions={'function': check_repo,
843 827 'method': ['DELETE']},
844 828 requirements=URL_NAME_REQUIREMENTS)
845 829
846 830 rmap.connect('pullrequest_show_all',
847 831 '/{repo_name}/pull-request',
848 832 controller='pullrequests',
849 833 action='show_all', conditions={'function': check_repo,
850 834 'method': ['GET']},
851 835 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
852 836
853 837 rmap.connect('pullrequest_comment',
854 838 '/{repo_name}/pull-request-comment/{pull_request_id}',
855 839 controller='pullrequests',
856 840 action='comment', conditions={'function': check_repo,
857 841 'method': ['POST']},
858 842 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
859 843
860 844 rmap.connect('pullrequest_comment_delete',
861 845 '/{repo_name}/pull-request-comment/{comment_id}/delete',
862 846 controller='pullrequests', action='delete_comment',
863 847 conditions={'function': check_repo, 'method': ['DELETE']},
864 848 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
865 849
866 850 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
867 851 controller='summary', conditions={'function': check_repo},
868 852 requirements=URL_NAME_REQUIREMENTS)
869 853
870 854 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
871 855 controller='changelog', conditions={'function': check_repo},
872 856 requirements=URL_NAME_REQUIREMENTS)
873 857
874 858 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
875 859 controller='changelog', action='changelog_summary',
876 860 conditions={'function': check_repo},
877 861 requirements=URL_NAME_REQUIREMENTS)
878 862
879 863 rmap.connect('changelog_file_home',
880 864 '/{repo_name}/changelog/{revision}/{f_path}',
881 865 controller='changelog', f_path=None,
882 866 conditions={'function': check_repo},
883 867 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
884 868
885 869 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
886 870 controller='changelog', action='changelog_elements',
887 871 conditions={'function': check_repo},
888 872 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
889 873
890 874 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
891 875 controller='files', revision='tip', f_path='',
892 876 conditions={'function': check_repo},
893 877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
894 878
895 879 rmap.connect('files_home_simple_catchrev',
896 880 '/{repo_name}/files/{revision}',
897 881 controller='files', revision='tip', f_path='',
898 882 conditions={'function': check_repo},
899 883 requirements=URL_NAME_REQUIREMENTS)
900 884
901 885 rmap.connect('files_home_simple_catchall',
902 886 '/{repo_name}/files',
903 887 controller='files', revision='tip', f_path='',
904 888 conditions={'function': check_repo},
905 889 requirements=URL_NAME_REQUIREMENTS)
906 890
907 891 rmap.connect('files_history_home',
908 892 '/{repo_name}/history/{revision}/{f_path}',
909 893 controller='files', action='history', revision='tip', f_path='',
910 894 conditions={'function': check_repo},
911 895 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 896
913 897 rmap.connect('files_authors_home',
914 898 '/{repo_name}/authors/{revision}/{f_path}',
915 899 controller='files', action='authors', revision='tip', f_path='',
916 900 conditions={'function': check_repo},
917 901 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
918 902
919 903 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
920 904 controller='files', action='diff', f_path='',
921 905 conditions={'function': check_repo},
922 906 requirements=URL_NAME_REQUIREMENTS)
923 907
924 908 rmap.connect('files_diff_2way_home',
925 909 '/{repo_name}/diff-2way/{f_path}',
926 910 controller='files', action='diff_2way', f_path='',
927 911 conditions={'function': check_repo},
928 912 requirements=URL_NAME_REQUIREMENTS)
929 913
930 914 rmap.connect('files_rawfile_home',
931 915 '/{repo_name}/rawfile/{revision}/{f_path}',
932 916 controller='files', action='rawfile', revision='tip',
933 917 f_path='', conditions={'function': check_repo},
934 918 requirements=URL_NAME_REQUIREMENTS)
935 919
936 920 rmap.connect('files_raw_home',
937 921 '/{repo_name}/raw/{revision}/{f_path}',
938 922 controller='files', action='raw', revision='tip', f_path='',
939 923 conditions={'function': check_repo},
940 924 requirements=URL_NAME_REQUIREMENTS)
941 925
942 926 rmap.connect('files_render_home',
943 927 '/{repo_name}/render/{revision}/{f_path}',
944 928 controller='files', action='index', revision='tip', f_path='',
945 929 rendered=True, conditions={'function': check_repo},
946 930 requirements=URL_NAME_REQUIREMENTS)
947 931
948 932 rmap.connect('files_annotate_home',
949 933 '/{repo_name}/annotate/{revision}/{f_path}',
950 934 controller='files', action='index', revision='tip',
951 935 f_path='', annotate=True, conditions={'function': check_repo},
952 936 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
953 937
954 938 rmap.connect('files_annotate_previous',
955 939 '/{repo_name}/annotate-previous/{revision}/{f_path}',
956 940 controller='files', action='annotate_previous', revision='tip',
957 941 f_path='', annotate=True, conditions={'function': check_repo},
958 942 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
959 943
960 944 rmap.connect('files_edit',
961 945 '/{repo_name}/edit/{revision}/{f_path}',
962 946 controller='files', action='edit', revision='tip',
963 947 f_path='',
964 948 conditions={'function': check_repo, 'method': ['POST']},
965 949 requirements=URL_NAME_REQUIREMENTS)
966 950
967 951 rmap.connect('files_edit_home',
968 952 '/{repo_name}/edit/{revision}/{f_path}',
969 953 controller='files', action='edit_home', revision='tip',
970 954 f_path='', conditions={'function': check_repo},
971 955 requirements=URL_NAME_REQUIREMENTS)
972 956
973 957 rmap.connect('files_add',
974 958 '/{repo_name}/add/{revision}/{f_path}',
975 959 controller='files', action='add', revision='tip',
976 960 f_path='',
977 961 conditions={'function': check_repo, 'method': ['POST']},
978 962 requirements=URL_NAME_REQUIREMENTS)
979 963
980 964 rmap.connect('files_add_home',
981 965 '/{repo_name}/add/{revision}/{f_path}',
982 966 controller='files', action='add_home', revision='tip',
983 967 f_path='', conditions={'function': check_repo},
984 968 requirements=URL_NAME_REQUIREMENTS)
985 969
986 970 rmap.connect('files_delete',
987 971 '/{repo_name}/delete/{revision}/{f_path}',
988 972 controller='files', action='delete', revision='tip',
989 973 f_path='',
990 974 conditions={'function': check_repo, 'method': ['POST']},
991 975 requirements=URL_NAME_REQUIREMENTS)
992 976
993 977 rmap.connect('files_delete_home',
994 978 '/{repo_name}/delete/{revision}/{f_path}',
995 979 controller='files', action='delete_home', revision='tip',
996 980 f_path='', conditions={'function': check_repo},
997 981 requirements=URL_NAME_REQUIREMENTS)
998 982
999 983 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1000 984 controller='files', action='archivefile',
1001 985 conditions={'function': check_repo},
1002 986 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1003 987
1004 988 rmap.connect('files_nodelist_home',
1005 989 '/{repo_name}/nodelist/{revision}/{f_path}',
1006 990 controller='files', action='nodelist',
1007 991 conditions={'function': check_repo},
1008 992 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1009 993
1010 994 rmap.connect('files_nodetree_full',
1011 995 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1012 996 controller='files', action='nodetree_full',
1013 997 conditions={'function': check_repo},
1014 998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1015 999
1016 1000 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1017 1001 controller='forks', action='fork_create',
1018 1002 conditions={'function': check_repo, 'method': ['POST']},
1019 1003 requirements=URL_NAME_REQUIREMENTS)
1020 1004
1021 1005 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1022 1006 controller='forks', action='fork',
1023 1007 conditions={'function': check_repo},
1024 1008 requirements=URL_NAME_REQUIREMENTS)
1025 1009
1026 1010 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1027 1011 controller='forks', action='forks',
1028 1012 conditions={'function': check_repo},
1029 1013 requirements=URL_NAME_REQUIREMENTS)
1030 1014
1031 1015 # must be here for proper group/repo catching pattern
1032 1016 _connect_with_slash(
1033 1017 rmap, 'repo_group_home', '/{group_name}',
1034 1018 controller='home', action='index_repo_group',
1035 1019 conditions={'function': check_group},
1036 1020 requirements=URL_NAME_REQUIREMENTS)
1037 1021
1038 1022 # catch all, at the end
1039 1023 _connect_with_slash(
1040 1024 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1041 1025 controller='summary', action='index',
1042 1026 conditions={'function': check_repo},
1043 1027 requirements=URL_NAME_REQUIREMENTS)
1044 1028
1045 1029 return rmap
1046 1030
1047 1031
1048 1032 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1049 1033 """
1050 1034 Connect a route with an optional trailing slash in `path`.
1051 1035 """
1052 1036 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1053 1037 mapper.connect(name, path, *args, **kwargs)
@@ -1,306 +1,306 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Journal / user event log controller for rhodecode
23 23 """
24 24
25 25 import logging
26 26 from itertools import groupby
27 27
28 28 from sqlalchemy import or_
29 29 from sqlalchemy.orm import joinedload
30 30
31 31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32 32
33 33 from webob.exc import HTTPBadRequest
34 34 from pylons import request, tmpl_context as c, response, url
35 35 from pylons.i18n.translation import _
36 36
37 from rhodecode.controllers.admin.admin import user_log_filter
38 37 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
39 38 from rhodecode.model.meta import Session
40 39 import rhodecode.lib.helpers as h
41 40 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.user_log_filter import user_log_filter
42 42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
43 43 from rhodecode.lib.base import BaseController, render
44 44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class JournalController(BaseController):
50 50
51 51 def __before__(self):
52 52 super(JournalController, self).__before__()
53 53 self.language = 'en-us'
54 54 self.ttl = "5"
55 55 self.feed_nr = 20
56 56 c.search_term = request.GET.get('filter')
57 57
58 58 def _get_daily_aggregate(self, journal):
59 59 groups = []
60 60 for k, g in groupby(journal, lambda x: x.action_as_day):
61 61 user_group = []
62 62 #groupby username if it's a present value, else fallback to journal username
63 63 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
64 64 l = list(g2)
65 65 user_group.append((l[0].user, l))
66 66
67 67 groups.append((k, user_group,))
68 68
69 69 return groups
70 70
71 71 def _get_journal_data(self, following_repos):
72 72 repo_ids = [x.follows_repository.repo_id for x in following_repos
73 73 if x.follows_repository is not None]
74 74 user_ids = [x.follows_user.user_id for x in following_repos
75 75 if x.follows_user is not None]
76 76
77 77 filtering_criterion = None
78 78
79 79 if repo_ids and user_ids:
80 80 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
81 81 UserLog.user_id.in_(user_ids))
82 82 if repo_ids and not user_ids:
83 83 filtering_criterion = UserLog.repository_id.in_(repo_ids)
84 84 if not repo_ids and user_ids:
85 85 filtering_criterion = UserLog.user_id.in_(user_ids)
86 86 if filtering_criterion is not None:
87 87 journal = self.sa.query(UserLog)\
88 88 .options(joinedload(UserLog.user))\
89 89 .options(joinedload(UserLog.repository))
90 90 #filter
91 91 try:
92 92 journal = user_log_filter(journal, c.search_term)
93 93 except Exception:
94 94 # we want this to crash for now
95 95 raise
96 96 journal = journal.filter(filtering_criterion)\
97 97 .order_by(UserLog.action_date.desc())
98 98 else:
99 99 journal = []
100 100
101 101 return journal
102 102
103 103 def _atom_feed(self, repos, public=True):
104 104 journal = self._get_journal_data(repos)
105 105 if public:
106 106 _link = url('public_journal_atom', qualified=True)
107 107 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
108 108 'atom feed')
109 109 else:
110 110 _link = url('journal_atom', qualified=True)
111 111 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
112 112
113 113 feed = Atom1Feed(title=_desc,
114 114 link=_link,
115 115 description=_desc,
116 116 language=self.language,
117 117 ttl=self.ttl)
118 118
119 119 for entry in journal[:self.feed_nr]:
120 120 user = entry.user
121 121 if user is None:
122 122 #fix deleted users
123 123 user = AttributeDict({'short_contact': entry.username,
124 124 'email': '',
125 125 'full_contact': ''})
126 126 action, action_extra, ico = h.action_parser(entry, feed=True)
127 127 title = "%s - %s %s" % (user.short_contact, action(),
128 128 entry.repository.repo_name)
129 129 desc = action_extra()
130 130 _url = None
131 131 if entry.repository is not None:
132 132 _url = url('changelog_home',
133 133 repo_name=entry.repository.repo_name,
134 134 qualified=True)
135 135
136 136 feed.add_item(title=title,
137 137 pubdate=entry.action_date,
138 138 link=_url or url('', qualified=True),
139 139 author_email=user.email,
140 140 author_name=user.full_contact,
141 141 description=desc)
142 142
143 143 response.content_type = feed.mime_type
144 144 return feed.writeString('utf-8')
145 145
146 146 def _rss_feed(self, repos, public=True):
147 147 journal = self._get_journal_data(repos)
148 148 if public:
149 149 _link = url('public_journal_atom', qualified=True)
150 150 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
151 151 'rss feed')
152 152 else:
153 153 _link = url('journal_atom', qualified=True)
154 154 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
155 155
156 156 feed = Rss201rev2Feed(title=_desc,
157 157 link=_link,
158 158 description=_desc,
159 159 language=self.language,
160 160 ttl=self.ttl)
161 161
162 162 for entry in journal[:self.feed_nr]:
163 163 user = entry.user
164 164 if user is None:
165 165 #fix deleted users
166 166 user = AttributeDict({'short_contact': entry.username,
167 167 'email': '',
168 168 'full_contact': ''})
169 169 action, action_extra, ico = h.action_parser(entry, feed=True)
170 170 title = "%s - %s %s" % (user.short_contact, action(),
171 171 entry.repository.repo_name)
172 172 desc = action_extra()
173 173 _url = None
174 174 if entry.repository is not None:
175 175 _url = url('changelog_home',
176 176 repo_name=entry.repository.repo_name,
177 177 qualified=True)
178 178
179 179 feed.add_item(title=title,
180 180 pubdate=entry.action_date,
181 181 link=_url or url('', qualified=True),
182 182 author_email=user.email,
183 183 author_name=user.full_contact,
184 184 description=desc)
185 185
186 186 response.content_type = feed.mime_type
187 187 return feed.writeString('utf-8')
188 188
189 189 @LoginRequired()
190 190 @NotAnonymous()
191 191 def index(self):
192 192 # Return a rendered template
193 193 p = safe_int(request.GET.get('page', 1), 1)
194 194 c.user = User.get(c.rhodecode_user.user_id)
195 195 following = self.sa.query(UserFollowing)\
196 196 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
197 197 .options(joinedload(UserFollowing.follows_repository))\
198 198 .all()
199 199
200 200 journal = self._get_journal_data(following)
201 201
202 202 def url_generator(**kw):
203 203 return url.current(filter=c.search_term, **kw)
204 204
205 205 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
206 206 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
207 207
208 208 c.journal_data = render('journal/journal_data.mako')
209 209 if request.is_xhr:
210 210 return c.journal_data
211 211
212 212 return render('journal/journal.mako')
213 213
214 214 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
215 215 @NotAnonymous()
216 216 def journal_atom(self):
217 217 """
218 218 Produce an atom-1.0 feed via feedgenerator module
219 219 """
220 220 following = self.sa.query(UserFollowing)\
221 221 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
222 222 .options(joinedload(UserFollowing.follows_repository))\
223 223 .all()
224 224 return self._atom_feed(following, public=False)
225 225
226 226 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
227 227 @NotAnonymous()
228 228 def journal_rss(self):
229 229 """
230 230 Produce an rss feed via feedgenerator module
231 231 """
232 232 following = self.sa.query(UserFollowing)\
233 233 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
234 234 .options(joinedload(UserFollowing.follows_repository))\
235 235 .all()
236 236 return self._rss_feed(following, public=False)
237 237
238 238 @CSRFRequired()
239 239 @LoginRequired()
240 240 @NotAnonymous()
241 241 def toggle_following(self):
242 242 user_id = request.POST.get('follows_user_id')
243 243 if user_id:
244 244 try:
245 245 self.scm_model.toggle_following_user(
246 246 user_id, c.rhodecode_user.user_id)
247 247 Session().commit()
248 248 return 'ok'
249 249 except Exception:
250 250 raise HTTPBadRequest()
251 251
252 252 repo_id = request.POST.get('follows_repo_id')
253 253 if repo_id:
254 254 try:
255 255 self.scm_model.toggle_following_repo(
256 256 repo_id, c.rhodecode_user.user_id)
257 257 Session().commit()
258 258 return 'ok'
259 259 except Exception:
260 260 raise HTTPBadRequest()
261 261
262 262
263 263 @LoginRequired()
264 264 def public_journal(self):
265 265 # Return a rendered template
266 266 p = safe_int(request.GET.get('page', 1), 1)
267 267
268 268 c.following = self.sa.query(UserFollowing)\
269 269 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
270 270 .options(joinedload(UserFollowing.follows_repository))\
271 271 .all()
272 272
273 273 journal = self._get_journal_data(c.following)
274 274
275 275 c.journal_pager = Page(journal, page=p, items_per_page=20)
276 276
277 277 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
278 278
279 279 c.journal_data = render('journal/journal_data.mako')
280 280 if request.is_xhr:
281 281 return c.journal_data
282 282 return render('journal/public_journal.mako')
283 283
284 284 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
285 285 def public_journal_atom(self):
286 286 """
287 287 Produce an atom-1.0 feed via feedgenerator module
288 288 """
289 289 c.following = self.sa.query(UserFollowing)\
290 290 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
291 291 .options(joinedload(UserFollowing.follows_repository))\
292 292 .all()
293 293
294 294 return self._atom_feed(c.following)
295 295
296 296 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
297 297 def public_journal_rss(self):
298 298 """
299 299 Produce an rss2 feed via feedgenerator module
300 300 """
301 301 c.following = self.sa.query(UserFollowing)\
302 302 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
303 303 .options(joinedload(UserFollowing.follows_repository))\
304 304 .all()
305 305
306 306 return self._rss_feed(c.following)
@@ -1,128 +1,134 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 18 pyroutes.register('gists', '/_admin/gists', []);
19 19 pyroutes.register('new_gist', '/_admin/gists/new', []);
20 20 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
21 21 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
22 22 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
23 23 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
24 24 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
25 25 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
26 26 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
27 27 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
28 28 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
29 29 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
30 30 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
31 31 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
32 32 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
33 33 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
34 34 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
35 35 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
36 36 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
37 37 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
38 38 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
39 39 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
40 40 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
41 41 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
42 42 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
43 43 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 44 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 45 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 46 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 47 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 48 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
49 49 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 50 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
51 51 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
52 52 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
53 53 pyroutes.register('favicon', '/favicon.ico', []);
54 54 pyroutes.register('robots', '/robots.txt', []);
55 55 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
56 56 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
57 57 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
58 58 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
59 59 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
60 60 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
61 61 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
62 62 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
63 63 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
64 64 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
65 65 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
66 66 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
67 67 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
68 68 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
69 69 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
70 70 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
71 71 pyroutes.register('ops_ping', '_admin/ops/ping', []);
72 pyroutes.register('admin_home', '/_admin', []);
73 pyroutes.register('admin_audit_logs', '_admin/audit_logs', []);
74 pyroutes.register('pull_requests_global_0', '_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
75 pyroutes.register('pull_requests_global_1', '_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
76 pyroutes.register('pull_requests_global', '_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
72 77 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
73 78 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
74 79 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
75 80 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
76 81 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
77 82 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
78 83 pyroutes.register('users', '_admin/users', []);
79 84 pyroutes.register('users_data', '_admin/users_data', []);
80 85 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
81 86 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
82 87 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
83 88 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
84 89 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
85 90 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
86 91 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
87 92 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
88 93 pyroutes.register('channelstream_proxy', '/_channelstream', []);
89 94 pyroutes.register('login', '/_admin/login', []);
90 95 pyroutes.register('logout', '/_admin/logout', []);
91 96 pyroutes.register('register', '/_admin/register', []);
92 97 pyroutes.register('reset_password', '/_admin/password_reset', []);
93 98 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
94 99 pyroutes.register('home', '/', []);
95 100 pyroutes.register('user_autocomplete_data', '/_users', []);
96 101 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
97 102 pyroutes.register('repo_list_data', '/_repos', []);
98 103 pyroutes.register('goto_switcher_data', '/_goto_data', []);
99 104 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
100 105 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
101 106 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
102 107 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
108 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
103 109 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
104 110 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
105 111 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
106 112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
107 113 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
108 114 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
109 115 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
110 116 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
111 117 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
112 118 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
113 119 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
114 120 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
115 121 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
116 122 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
117 123 pyroutes.register('search', '/_admin/search', []);
118 124 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
119 125 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
120 126 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
121 127 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
122 128 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
123 129 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
124 130 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
125 131 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
126 132 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
127 133 pyroutes.register('apiv2', '/_admin/api', []);
128 134 }
@@ -1,40 +1,40 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 ${_('Admin journal')}
5 ${_('Admin audit logs')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 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="${_('filter...')}"/>
14 14 <input type='submit' value="${_('filter')}" class="btn" />
15 ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
15 ${_('Audit logs')} - ${_ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
16 16 ${h.end_form()}
17 17 <p class="tooltip filterexample" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
18 18 </%def>
19 19
20 20 <%def name="menu_bar_nav()">
21 21 ${self.menu_items(active='admin')}
22 22 </%def>
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <!-- box / title -->
26 26 <div class="title">
27 27 ${self.breadcrumbs()}
28 28 </div>
29 29 <!-- end box / title -->
30 30 <div class="table">
31 31 <div id="user_log">
32 ${c.log_data}
32 <%include file="/admin/admin_log_base.mako" />
33 33 </div>
34 34 </div>
35 35 </div>
36 36
37 37 <script>
38 38 $('#j_filter').autoGrowInput();
39 39 </script>
40 40 </%def>
@@ -1,116 +1,116 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Authentication Settings')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${_('Authentication Plugins')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27
28 28 <div class='sidebar-col-wrapper'>
29 29
30 30 <div class="sidebar">
31 31 <ul class="nav nav-pills nav-stacked">
32 32 % for item in resource.get_root().get_nav_list():
33 33 <li ${'class=active' if item == resource else ''}>
34 34 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
35 35 </li>
36 36 % endfor
37 37 </ul>
38 38 </div>
39 39
40 40 <div class="main-content-full-width">
41 41 ${h.secure_form(request.resource_path(resource, route_name='auth_home'))}
42 42 <div class="form">
43 43
44 44 <div class="panel panel-default">
45 45
46 46 <div class="panel-heading">
47 47 <h3 class="panel-title">${_("Enabled and Available Plugins")}</h3>
48 48 </div>
49 49
50 50 <div class="fields panel-body">
51 51
52 52 <div class="field">
53 53 <div class="label">${_("Enabled Plugins")}</div>
54 54 <div class="textarea text-area editor">
55 55 ${h.textarea('auth_plugins',cols=23,rows=5,class_="medium")}
56 56 </div>
57 57 <p class="help-block">
58 58 ${_('Add a list of plugins, separated by commas. '
59 59 'The order of the plugins is also the order in which '
60 60 'RhodeCode Enterprise will try to authenticate a user.')}
61 61 </p>
62 62 </div>
63 63
64 64 <div class="field">
65 65 <div class="label">${_('Available Built-in Plugins')}</div>
66 66 <ul class="auth_plugins">
67 67 %for plugin in available_plugins:
68 68 <li>
69 69 <div class="auth_buttons">
70 70 <span plugin_id="${plugin.get_id()}" class="toggle-plugin btn ${'btn-success' if plugin.get_id() in enabled_plugins else ''}">
71 71 ${_('enabled') if plugin.get_id() in enabled_plugins else _('disabled')}
72 72 </span>
73 73 ${plugin.get_display_name()} (${plugin.get_id()})
74 74 </div>
75 75 </li>
76 76 %endfor
77 77 </ul>
78 78 </div>
79 79
80 80 <div class="buttons">
81 81 ${h.submit('save',_('Save'),class_="btn")}
82 82 </div>
83 83 </div>
84 84 </div>
85 85 </div>
86 86 ${h.end_form()}
87 87 </div>
88 88 </div>
89 89 </div>
90 90
91 91 <script>
92 92 $('.toggle-plugin').click(function(e){
93 93 var auth_plugins_input = $('#auth_plugins');
94 94 var notEmpty = function(element, index, array) {
95 95 return (element != "");
96 96 };
97 97 var elems = auth_plugins_input.val().split(',').filter(notEmpty);
98 98 var cur_button = e.currentTarget;
99 99 var plugin_id = $(cur_button).attr('plugin_id');
100 100 if($(cur_button).hasClass('btn-success')){
101 101 elems.splice(elems.indexOf(plugin_id), 1);
102 102 auth_plugins_input.val(elems.join(','));
103 103 $(cur_button).removeClass('btn-success');
104 104 cur_button.innerHTML = _gettext('disabled');
105 105 }
106 106 else{
107 107 if(elems.indexOf(plugin_id) == -1){
108 108 elems.push(plugin_id);
109 109 }
110 110 auth_plugins_input.val(elems.join(','));
111 111 $(cur_button).addClass('btn-success');
112 112 cur_button.innerHTML = _gettext('enabled');
113 113 }
114 114 });
115 115 </script>
116 116 </%def>
@@ -1,118 +1,118 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Authentication Settings')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
15 15 &raquo;
16 16 ${resource.display_name}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='admin')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28 <div class='sidebar-col-wrapper'>
29 29
30 30 ## TODO: This is repeated in the auth root template and should be merged
31 31 ## into a single solution.
32 32 <div class="sidebar">
33 33 <ul class="nav nav-pills nav-stacked">
34 34 % for item in resource.get_root().get_nav_list():
35 35 <li ${'class=active' if item == resource else ''}>
36 36 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
37 37 </li>
38 38 % endfor
39 39 </ul>
40 40 </div>
41 41
42 42 <div class="main-content-full-width">
43 43 <div class="panel panel-default">
44 44 <div class="panel-heading">
45 45 <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3>
46 46 </div>
47 47 <div class="panel-body">
48 48 <div class="plugin_form">
49 49 <div class="fields">
50 50 ${h.secure_form(request.resource_path(resource, route_name='auth_home'))}
51 51 <div class="form">
52 52
53 53 %for node in plugin.get_settings_schema():
54 54 <% label_css_class = ("label-checkbox" if (node.widget == "bool") else "") %>
55 55 <div class="field">
56 56 <div class="label ${label_css_class}"><label for="${node.name}">${node.title}</label></div>
57 57 <div class="input">
58 58 %if node.widget in ["string", "int", "unicode"]:
59 59 ${h.text(node.name, defaults.get(node.name), class_="medium")}
60 60 %elif node.widget == "password":
61 61 ${h.password(node.name, defaults.get(node.name), class_="medium")}
62 62 %elif node.widget == "bool":
63 63 <div class="checkbox">${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
64 64 %elif node.widget == "select":
65 65 ${h.select(node.name, defaults.get(node.name), node.validator.choices)}
66 66 %elif node.widget == "readonly":
67 67 ${node.default}
68 68 %else:
69 69 This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select].
70 70 %endif
71 71 %if node.name in errors:
72 72 <span class="error-message">${errors.get(node.name)}</span>
73 73 <br />
74 74 %endif
75 75 <p class="help-block pre-formatting">${node.description}</p>
76 76 </div>
77 77 </div>
78 78 %endfor
79 79
80 80 ## Allow derived templates to add something below the form
81 81 ## input fields
82 82 %if hasattr(next, 'below_form_fields'):
83 83 ${next.below_form_fields()}
84 84 %endif
85 85
86 86 <div class="buttons">
87 87 ${h.submit('save',_('Save'),class_="btn")}
88 88 </div>
89 89
90 90 </div>
91 91 ${h.end_form()}
92 92 </div>
93 93 </div>
94 94 </div>
95 95 </div>
96 96 </div>
97 97
98 98 </div>
99 99 </div>
100 100
101 101 ## TODO: Ugly hack to get ldap select elements to work.
102 102 ## Find a solution to integrate this nicely.
103 103 <script>
104 104 $(document).ready(function() {
105 105 var select2Options = {
106 106 containerCssClass: 'drop-menu',
107 107 dropdownCssClass: 'drop-menu-dropdown',
108 108 dropdownAutoWidth: true,
109 109 minimumResultsForSearch: -1
110 110 };
111 111 $("#tls_kind").select2(select2Options);
112 112 $("#tls_reqcert").select2(select2Options);
113 113 $("#search_scope").select2(select2Options);
114 114 $("#group_extraction_type").select2(select2Options);
115 115 $("#admin_groups_sync").select2(select2Options);
116 116 });
117 117 </script>
118 118 </%def>
@@ -1,42 +1,42 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repositories defaults')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${_('Repositories defaults')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26
27 27 ##main
28 28 <div class="sidebar-col-wrapper">
29 29 <div class="sidebar">
30 30 <ul class="nav nav-pills nav-stacked">
31 31 <li class="${'active' if c.active=='repositories' else ''}"><a href="${h.url('admin_defaults_repositories')}">${_('Repository')}</a></li>
32 32 </ul>
33 33 </div>
34 34
35 35 <div class="main-content-full-width">
36 36 <%include file="/admin/defaults/defaults_${c.active}.mako"/>
37 37 </div>
38 38
39 39 </div>
40 40 </div>
41 41
42 42 </%def>
@@ -1,42 +1,42 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%!
3 3 def inherit(context):
4 4 if context['c'].repo:
5 5 return "/admin/repos/repo_edit.mako"
6 6 elif context['c'].repo_group:
7 7 return "/admin/repo_groups/repo_group_edit.mako"
8 8 else:
9 9 return "/admin/settings/settings.mako"
10 10 %>
11 11 <%inherit file="${inherit(context)}" />
12 12
13 13 <%def name="title()">
14 14 ${_('Integrations Settings')}
15 15 %if c.rhodecode_name:
16 16 &middot; ${h.branding(c.rhodecode_name)}
17 17 %endif
18 18 </%def>
19 19
20 20 <%def name="breadcrumbs_links()">
21 ${h.link_to(_('Admin'),h.url('admin_home'))}
21 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
22 22 &raquo;
23 23 ${_('Integrations')}
24 24 </%def>
25 25
26 26 <%def name="menu_bar_nav()">
27 27 %if c.repo:
28 28 ${self.menu_items(active='repositories')}
29 29 %else:
30 30 ${self.menu_items(active='admin')}
31 31 %endif
32 32 </%def>
33 33
34 34 <%def name="menu_bar_subnav()">
35 35 %if c.repo:
36 36 ${self.repo_menu(active='options')}
37 37 %endif
38 38 </%def>
39 39
40 40 <%def name="main_content()">
41 41 ${next.body()}
42 42 </%def>
@@ -1,69 +1,69 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 %if c.repo:
6 6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 7 &raquo;
8 8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 9 &raquo;
10 10 ${h.link_to(current_IntegrationType.display_name,
11 11 request.route_url(route_name='repo_integrations_list',
12 12 repo_name=c.repo.repo_name,
13 13 integration=current_IntegrationType.key))}
14 14 %elif c.repo_group:
15 ${h.link_to(_('Admin'),h.url('admin_home'))}
15 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
16 16 &raquo;
17 17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
18 18 &raquo;
19 19 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
20 20 &raquo;
21 21 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
22 22 &raquo;
23 23 ${h.link_to(current_IntegrationType.display_name,
24 24 request.route_url(route_name='repo_group_integrations_list',
25 25 repo_group_name=c.repo_group.group_name,
26 26 integration=current_IntegrationType.key))}
27 27 %else:
28 ${h.link_to(_('Admin'),h.url('admin_home'))}
28 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
29 29 &raquo;
30 30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
31 31 &raquo;
32 32 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
33 33 &raquo;
34 34 ${h.link_to(current_IntegrationType.display_name,
35 35 request.route_url(route_name='global_integrations_list',
36 36 integration=current_IntegrationType.key))}
37 37 %endif
38 38
39 39 %if integration:
40 40 &raquo;
41 41 ${integration.name}
42 42 %elif current_IntegrationType:
43 43 &raquo;
44 44 ${current_IntegrationType.display_name}
45 45 %endif
46 46 </%def>
47 47
48 48 <style>
49 49 .control-inputs.item-options, .control-inputs.item-settings {
50 50 float: left;
51 51 width: 100%;
52 52 }
53 53 </style>
54 54 <div class="panel panel-default">
55 55 <div class="panel-heading">
56 56 <h2 class="panel-title">
57 57 %if integration:
58 58 ${current_IntegrationType.display_name} - ${integration.name}
59 59 %else:
60 60 ${_('Create New %(integration_type)s Integration') % {
61 61 'integration_type': current_IntegrationType.display_name
62 62 }}
63 63 %endif
64 64 </h2>
65 65 </div>
66 66 <div class="panel-body">
67 67 ${form.render() | n}
68 68 </div>
69 69 </div>
@@ -1,252 +1,252 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 %if c.repo:
6 6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 7 %elif c.repo_group:
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
13 13 %else:
14 ${h.link_to(_('Admin'),h.url('admin_home'))}
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 15 &raquo;
16 16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 17 %endif
18 18 %if current_IntegrationType:
19 19 &raquo;
20 20 %if c.repo:
21 21 ${h.link_to(_('Integrations'),
22 22 request.route_url(route_name='repo_integrations_home',
23 23 repo_name=c.repo.repo_name))}
24 24 %elif c.repo_group:
25 25 ${h.link_to(_('Integrations'),
26 26 request.route_url(route_name='repo_group_integrations_home',
27 27 repo_group_name=c.repo_group.group_name))}
28 28 %else:
29 29 ${h.link_to(_('Integrations'),
30 30 request.route_url(route_name='global_integrations_home'))}
31 31 %endif
32 32 &raquo;
33 33 ${current_IntegrationType.display_name}
34 34 %else:
35 35 &raquo;
36 36 ${_('Integrations')}
37 37 %endif
38 38 </%def>
39 39
40 40 <div class="panel panel-default">
41 41 <div class="panel-heading">
42 42 <h3 class="panel-title">
43 43 %if c.repo:
44 44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 45 %elif c.repo_group:
46 46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
47 47 %else:
48 48 ${_('Current Integrations')}
49 49 %endif
50 50 </h3>
51 51 </div>
52 52 <div class="panel-body">
53 53 <%
54 54 if c.repo:
55 55 home_url = request.route_path('repo_integrations_home',
56 56 repo_name=c.repo.repo_name)
57 57 elif c.repo_group:
58 58 home_url = request.route_path('repo_group_integrations_home',
59 59 repo_group_name=c.repo_group.group_name)
60 60 else:
61 61 home_url = request.route_path('global_integrations_home')
62 62 %>
63 63
64 64 <a href="${home_url}" class="btn ${not current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
65 65
66 66 %for integration_key, IntegrationType in available_integrations.items():
67 67 <%
68 68 if c.repo:
69 69 list_url = request.route_path('repo_integrations_list',
70 70 repo_name=c.repo.repo_name,
71 71 integration=integration_key)
72 72 elif c.repo_group:
73 73 list_url = request.route_path('repo_group_integrations_list',
74 74 repo_group_name=c.repo_group.group_name,
75 75 integration=integration_key)
76 76 else:
77 77 list_url = request.route_path('global_integrations_list',
78 78 integration=integration_key)
79 79 %>
80 80 <a href="${list_url}"
81 81 class="btn ${current_IntegrationType and integration_key == current_IntegrationType.key and 'btn-primary' or ''}">
82 82 ${IntegrationType.display_name}
83 83 </a>
84 84 %endfor
85 85
86 86 <%
87 87 if c.repo:
88 88 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
89 89 elif c.repo_group:
90 90 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
91 91 else:
92 92 create_url = h.route_path('global_integrations_new')
93 93 %>
94 94 <p class="pull-right">
95 95 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
96 96 </p>
97 97
98 98 <table class="rctable integrations">
99 99 <thead>
100 100 <tr>
101 101 <th><a href="?sort=enabled:${rev_sort_dir}">${_('Enabled')}</a></th>
102 102 <th><a href="?sort=name:${rev_sort_dir}">${_('Name')}</a></th>
103 103 <th colspan="2"><a href="?sort=integration_type:${rev_sort_dir}">${_('Type')}</a></th>
104 104 <th><a href="?sort=scope:${rev_sort_dir}">${_('Scope')}</a></th>
105 105 <th>${_('Actions')}</th>
106 106 <th></th>
107 107 </tr>
108 108 </thead>
109 109 <tbody>
110 110 %if not integrations_list:
111 111 <tr>
112 112 <td colspan="7">
113 113 <% integration_type = current_IntegrationType and current_IntegrationType.display_name or '' %>
114 114 %if c.repo:
115 115 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
116 116 %elif c.repo_group:
117 117 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
118 118 %else:
119 119 ${_('No {type} integrations exist yet.').format(type=integration_type)}
120 120 %endif
121 121
122 122 %if current_IntegrationType:
123 123 <%
124 124 if c.repo:
125 125 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=current_IntegrationType.key)
126 126 elif c.repo_group:
127 127 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=current_IntegrationType.key)
128 128 else:
129 129 create_url = h.route_path('global_integrations_create', integration=current_IntegrationType.key)
130 130 %>
131 131 %endif
132 132
133 133 <a href="${create_url}">${_(u'Create one')}</a>
134 134 </td>
135 135 </tr>
136 136 %endif
137 137 %for IntegrationType, integration in integrations_list:
138 138 <tr id="integration_${integration.integration_id}">
139 139 <td class="td-enabled">
140 140 %if integration.enabled:
141 141 <div class="flag_status approved pull-left"></div>
142 142 %else:
143 143 <div class="flag_status rejected pull-left"></div>
144 144 %endif
145 145 </td>
146 146 <td class="td-description">
147 147 ${integration.name}
148 148 </td>
149 149 <td class="td-icon">
150 150 %if integration.integration_type in available_integrations:
151 151 <div class="integration-icon">
152 152 ${available_integrations[integration.integration_type].icon|n}
153 153 </div>
154 154 %else:
155 155 ?
156 156 %endif
157 157 </td>
158 158 <td class="td-type">
159 159 ${integration.integration_type}
160 160 </td>
161 161 <td class="td-scope">
162 162 %if integration.repo:
163 163 <a href="${h.url('summary_home', repo_name=integration.repo.repo_name)}">
164 164 ${_('repo')}:${integration.repo.repo_name}
165 165 </a>
166 166 %elif integration.repo_group:
167 167 <a href="${h.url('repo_group_home', group_name=integration.repo_group.group_name)}">
168 168 ${_('repogroup')}:${integration.repo_group.group_name}
169 169 %if integration.child_repos_only:
170 170 ${_('child repos only')}
171 171 %else:
172 172 ${_('cascade to all')}
173 173 %endif
174 174 </a>
175 175 %else:
176 176 %if integration.child_repos_only:
177 177 ${_('top level repos only')}
178 178 %else:
179 179 ${_('global')}
180 180 %endif
181 181 </td>
182 182 %endif
183 183 <td class="td-action">
184 184 %if not IntegrationType:
185 185 ${_('unknown integration')}
186 186 %else:
187 187 <%
188 188 if c.repo:
189 189 edit_url = request.route_path('repo_integrations_edit',
190 190 repo_name=c.repo.repo_name,
191 191 integration=integration.integration_type,
192 192 integration_id=integration.integration_id)
193 193 elif c.repo_group:
194 194 edit_url = request.route_path('repo_group_integrations_edit',
195 195 repo_group_name=c.repo_group.group_name,
196 196 integration=integration.integration_type,
197 197 integration_id=integration.integration_id)
198 198 else:
199 199 edit_url = request.route_path('global_integrations_edit',
200 200 integration=integration.integration_type,
201 201 integration_id=integration.integration_id)
202 202 %>
203 203 <div class="grid_edit">
204 204 <a href="${edit_url}">${_('Edit')}</a>
205 205 </div>
206 206 <div class="grid_delete">
207 207 <a href="${edit_url}"
208 208 class="btn btn-link btn-danger delete_integration_entry"
209 209 data-desc="${integration.name}"
210 210 data-uid="${integration.integration_id}">
211 211 ${_('Delete')}
212 212 </a>
213 213 </div>
214 214 %endif
215 215 </td>
216 216 </tr>
217 217 %endfor
218 218 <tr id="last-row"></tr>
219 219 </tbody>
220 220 </table>
221 221 <div class="integrations-paginator">
222 222 <div class="pagination-wh pagination-left">
223 223 ${integrations_list.pager('$link_previous ~2~ $link_next')}
224 224 </div>
225 225 </div>
226 226 </div>
227 227 </div>
228 228 <script type="text/javascript">
229 229 var delete_integration = function(entry) {
230 230 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
231 231 var request = $.ajax({
232 232 type: "POST",
233 233 url: $(entry).attr('href'),
234 234 data: {
235 235 'delete': 'delete',
236 236 'csrf_token': CSRF_TOKEN
237 237 },
238 238 success: function(){
239 239 location.reload();
240 240 },
241 241 error: function(data, textStatus, errorThrown){
242 242 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
243 243 }
244 244 });
245 245 };
246 246 };
247 247
248 248 $('.delete_integration_entry').on('click', function(e){
249 249 e.preventDefault();
250 250 delete_integration(this);
251 251 });
252 252 </script> No newline at end of file
@@ -1,66 +1,66 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="widgets" file="/widgets.mako"/>
4 4
5 5 <%def name="breadcrumbs_links()">
6 6 %if c.repo:
7 7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
8 8 &raquo;
9 9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 10 %elif c.repo_group:
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 12 &raquo;
13 13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 14 &raquo;
15 15 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
16 16 &raquo;
17 17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 18 %else:
19 ${h.link_to(_('Admin'),h.url('admin_home'))}
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 20 &raquo;
21 21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 22 &raquo;
23 23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
24 24 %endif
25 25 &raquo;
26 26 ${_('Create new integration')}
27 27 </%def>
28 28 <%widgets:panel class_='integrations'>
29 29 <%def name="title()">
30 30 %if c.repo:
31 31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
32 32 %elif c.repo_group:
33 33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
34 34 %else:
35 35 ${_('Create New Global Integration')}
36 36 %endif
37 37 </%def>
38 38
39 39 %for integration, IntegrationType in available_integrations.items():
40 40 <%
41 41 if c.repo:
42 42 create_url = request.route_path('repo_integrations_create',
43 43 repo_name=c.repo.repo_name,
44 44 integration=integration)
45 45 elif c.repo_group:
46 46 create_url = request.route_path('repo_group_integrations_create',
47 47 repo_group_name=c.repo_group.group_name,
48 48 integration=integration)
49 49 else:
50 50 create_url = request.route_path('global_integrations_create',
51 51 integration=integration)
52 52 %>
53 53 <a href="${create_url}" class="integration-box">
54 54 <%widgets:panel>
55 55 <h2>
56 56 <div class="integration-icon">
57 57 ${IntegrationType.icon|n}
58 58 </div>
59 59 ${IntegrationType.display_name}
60 60 </h2>
61 61 ${IntegrationType.description or _('No description available')}
62 62 </%widgets:panel>
63 63 </a>
64 64 %endfor
65 65 <div style="clear:both"></div>
66 66 </%widgets:panel>
@@ -1,56 +1,56 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Permissions Administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${_('Permissions')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27
28 28 <div class="sidebar-col-wrapper scw-small">
29 29 ##main
30 30 <div class="sidebar">
31 31 <ul class="nav nav-pills nav-stacked">
32 32 <li class="${'active' if c.active=='application' else ''}">
33 33 <a href="${h.url('admin_permissions_application')}">${_('Application')}</a>
34 34 </li>
35 35 <li class="${'active' if c.active=='global' else ''}">
36 36 <a href="${h.url('admin_permissions_global')}">${_('Global')}</a>
37 37 </li>
38 38 <li class="${'active' if c.active=='objects' else ''}">
39 39 <a href="${h.url('admin_permissions_object')}">${_('Object')}</a>
40 40 </li>
41 41 <li class="${'active' if c.active=='ips' else ''}">
42 42 <a href="${h.url('admin_permissions_ips')}">${_('IP Whitelist')}</a>
43 43 </li>
44 44 <li class="${'active' if c.active=='perms' else ''}">
45 45 <a href="${h.url('admin_permissions_overview')}">${_('Overview')}</a>
46 46 </li>
47 47 </ul>
48 48 </div>
49 49
50 50 <div class="main-content-full-width">
51 51 <%include file="/admin/permissions/permissions_${c.active}.mako"/>
52 52 </div>
53 53 </div>
54 54 </div>
55 55
56 56 </%def>
@@ -1,100 +1,100 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add repository group')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
15 15 &raquo;
16 16 ${_('Add Repository Group')}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='admin')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <!-- box / title -->
26 26 <div class="title">
27 27 ${self.breadcrumbs()}
28 28 </div>
29 29 <!-- end box / title -->
30 30 ${h.secure_form(url('repo_groups'), method='post')}
31 31 <div class="form">
32 32 <!-- fields -->
33 33 <div class="fields">
34 34 <div class="field">
35 35 <div class="label">
36 36 <label for="group_name">${_('Group Name')}:</label>
37 37 </div>
38 38 <div class="input">
39 39 ${h.text('group_name', class_="medium")}
40 40 </div>
41 41 </div>
42 42
43 43 <div class="field">
44 44 <div class="label">
45 45 <label for="group_description">${_('Description')}:</label>
46 46 </div>
47 47 <div class="textarea editor">
48 48 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
49 49 </div>
50 50 </div>
51 51
52 52 <div class="field">
53 53 <div class="label">
54 54 <label for="group_parent_id">${_('Group Parent')}:</label>
55 55 </div>
56 56 <div class="select">
57 57 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
58 58 </div>
59 59 </div>
60 60
61 61 <div id="copy_perms" class="field">
62 62 <div class="label label-checkbox">
63 63 <label for="group_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
64 64 </div>
65 65 <div class="checkboxes">
66 66 ${h.checkbox('group_copy_permissions', value="True", checked="checked")}
67 67 <span class="help-block">${_('Copy permission settings from parent repository group.')}</span>
68 68 </div>
69 69 </div>
70 70
71 71 <div class="buttons">
72 72 ${h.submit('save',_('Save'),class_="btn")}
73 73 </div>
74 74 </div>
75 75 </div>
76 76 ${h.end_form()}
77 77 </div>
78 78 <script>
79 79 $(document).ready(function(){
80 80 var setCopyPermsOption = function(group_val){
81 81 if(group_val != "-1"){
82 82 $('#copy_perms').show()
83 83 }
84 84 else{
85 85 $('#copy_perms').hide();
86 86 }
87 87 }
88 88 $("#group_parent_id").select2({
89 89 'containerCssClass': "drop-menu",
90 90 'dropdownCssClass': "drop-menu-dropdown",
91 91 'dropdownAutoWidth': true
92 92 });
93 93 setCopyPermsOption($('#group_parent_id').val())
94 94 $("#group_parent_id").on("change", function(e) {
95 95 setCopyPermsOption(e.val)
96 96 })
97 97 $('#group_name').focus();
98 98 })
99 99 </script>
100 100 </%def>
@@ -1,61 +1,61 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s repository group settings') % c.repo_group.name}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
15 15 %if c.repo_group.parent_group:
16 16 &raquo; ${h.link_to(c.repo_group.parent_group.name,h.url('repo_group_home',group_name=c.repo_group.parent_group.group_name))}
17 17 %endif
18 18 &raquo; ${c.repo_group.name}
19 19 </%def>
20 20
21 21 <%def name="breadcrumbs_side_links()">
22 22 <ul class="links">
23 23 <li>
24 24 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success">${_(u'Add Child Group')}</a>
25 25 </li>
26 26 </ul>
27 27 </%def>
28 28
29 29 <%def name="menu_bar_nav()">
30 30 ${self.menu_items(active='admin')}
31 31 </%def>
32 32
33 33 <%def name="main_content()">
34 34 <%include file="/admin/repo_groups/repo_group_edit_${c.active}.mako"/>
35 35 </%def>
36 36
37 37 <%def name="main()">
38 38 <div class="box">
39 39 <div class="title">
40 40 ${self.breadcrumbs()}
41 41 ${self.breadcrumbs_side_links()}
42 42 </div>
43 43
44 44 <div class="sidebar-col-wrapper">
45 45 ##main
46 46 <div class="sidebar">
47 47 <ul class="nav nav-pills nav-stacked">
48 48 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_repo_group', group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
49 49 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_repo_group_perms', group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
50 50 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_repo_group_advanced', group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
51 51 <li class="${'active' if c.active=='integrations' else ''}"><a href="${h.route_path('repo_group_integrations_home', repo_group_name=c.repo_group.group_name)}">${_('Integrations')}</a></li>
52 52 </ul>
53 53 </div>
54 54
55 55 <div class="main-content-full-width">
56 56 ${self.main_content()}
57 57 </div>
58 58
59 59 </div>
60 60 </div>
61 61 </%def>
@@ -1,94 +1,94 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repository groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 <ul class="links">
25 25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
26 26 <li>
27 27 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
28 28 </li>
29 29 %endif
30 30 </ul>
31 31 </div>
32 32 <div id="repos_list_wrap">
33 33 <table id="group_list_table" class="display"></table>
34 34 </div>
35 35 </div>
36 36
37 37 <script>
38 38 $(document).ready(function() {
39 39
40 40 var get_datatable_count = function(){
41 41 var api = $('#group_list_table').dataTable().api();
42 42 $('#repo_group_count').text(api.page.info().recordsDisplay);
43 43 };
44 44
45 45 // repo group list
46 46 $('#group_list_table').DataTable({
47 47 data: ${c.data|n},
48 48 dom: 'rtp',
49 49 pageLength: ${c.visual.admin_grid_items},
50 50 order: [[ 0, "asc" ]],
51 51 columns: [
52 52 { data: {"_": "name",
53 53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 55 { data: {"_": "desc",
56 56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 57 { data: {"_": "top_level_repos",
58 58 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
59 59 { data: {"_": "owner",
60 60 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
61 61 { data: {"_": "action",
62 62 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
63 63 ],
64 64 language: {
65 65 paginate: DEFAULT_GRID_PAGINATION,
66 66 emptyTable: _gettext("No repository groups available yet.")
67 67 },
68 68 "initComplete": function( settings, json ) {
69 69 get_datatable_count();
70 70 quick_repo_menu();
71 71 }
72 72 });
73 73
74 74 // update the counter when doing search
75 75 $('#group_list_table').on( 'search.dt', function (e,settings) {
76 76 get_datatable_count();
77 77 });
78 78
79 79 // filter, filter both grids
80 80 $('#q_filter').on( 'keyup', function () {
81 81
82 82 var repo_group_api = $('#group_list_table').dataTable().api();
83 83 repo_group_api
84 84 .columns(0)
85 85 .search(this.value)
86 86 .draw();
87 87 });
88 88
89 89 // refilter table if page load via back button
90 90 $("#q_filter").trigger('keyup');
91 91 });
92 92 </script>
93 93 </%def>
94 94
@@ -1,37 +1,37 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add repository')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 %if c.rhodecode_user.is_admin:
13 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 14 &raquo;
15 15 ${h.link_to(_('Repositories'),h.url('repos'))}
16 16 %else:
17 17 ${_('Admin')}
18 18 &raquo;
19 19 ${_('Repositories')}
20 20 %endif
21 21 &raquo;
22 22 ${_('Add Repository')}
23 23 </%def>
24 24
25 25 <%def name="menu_bar_nav()">
26 26 ${self.menu_items(active='admin')}
27 27 </%def>
28 28
29 29 <%def name="main()">
30 30 <div class="box">
31 31 <!-- box / title -->
32 32 <div class="title">
33 33 ${self.breadcrumbs()}
34 34 </div>
35 35 <%include file="repo_add_base.mako"/>
36 36 </div>
37 37 </%def>
@@ -1,101 +1,101 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repositories administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 <ul class="links">
25 25 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
26 26 <li>
27 27 <a href="${h.url('new_repo')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
28 28 </li>
29 29 %endif
30 30 </ul>
31 31 </div>
32 32 <div id="repos_list_wrap">
33 33 <table id="repo_list_table" class="display"></table>
34 34 </div>
35 35 </div>
36 36
37 37 <script>
38 38 $(document).ready(function() {
39 39
40 40 var get_datatable_count = function(){
41 41 var api = $('#repo_list_table').dataTable().api();
42 42 $('#repo_count').text(api.page.info().recordsDisplay);
43 43 };
44 44
45 45
46 46 // repo list
47 47 $('#repo_list_table').DataTable({
48 48 data: ${c.data|n},
49 49 dom: 'rtp',
50 50 pageLength: ${c.visual.admin_grid_items},
51 51 order: [[ 0, "asc" ]],
52 52 columns: [
53 53 { data: {"_": "name",
54 54 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
55 55 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
56 56 { data: {"_": "desc",
57 57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
58 58 { data: {"_": "last_change",
59 59 "sort": "last_change_raw",
60 60 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
61 61 { data: {"_": "last_changeset",
62 62 "sort": "last_changeset_raw",
63 63 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
64 64 { data: {"_": "owner",
65 65 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
66 66 { data: {"_": "state",
67 67 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
68 68 { data: {"_": "action",
69 69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
70 70 ],
71 71 language: {
72 72 paginate: DEFAULT_GRID_PAGINATION,
73 73 emptyTable:_gettext("No repositories available yet.")
74 74 },
75 75 "initComplete": function( settings, json ) {
76 76 get_datatable_count();
77 77 quick_repo_menu();
78 78 }
79 79 });
80 80
81 81 // update the counter when doing search
82 82 $('#repo_list_table').on( 'search.dt', function (e,settings) {
83 83 get_datatable_count();
84 84 });
85 85
86 86 // filter, filter both grids
87 87 $('#q_filter').on( 'keyup', function () {
88 88 var repo_api = $('#repo_list_table').dataTable().api();
89 89 repo_api
90 90 .columns(0)
91 91 .search(this.value)
92 92 .draw();
93 93 });
94 94
95 95 // refilter table if page load via back button
96 96 $("#q_filter").trigger('keyup');
97 97 });
98 98
99 99 </script>
100 100
101 101 </%def>
@@ -1,53 +1,53 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${_('Settings')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21 <%def name="side_bar_nav()">
22 22 % for navitem in c.navlist:
23 23 <li class="${'active' if c.active==navitem.key else ''}">
24 24 <a href="${navitem.url}">${navitem.name}</a>
25 25 </li>
26 26 % endfor
27 27 </%def>
28 28
29 29 <%def name="main_content()">
30 30 <%include file="/admin/settings/settings_${c.active}.mako"/>
31 31 </%def>
32 32
33 33 <%def name="main()">
34 34 <div class="box">
35 35 <div class="title">
36 36 ${self.breadcrumbs()}
37 37 </div>
38 38
39 39 ##main
40 40 <div class='sidebar-col-wrapper'>
41 41 <div class="sidebar">
42 42 <ul class="nav nav-pills nav-stacked">
43 43 ${self.side_bar_nav()}
44 44 </ul>
45 45 </div>
46 46
47 47 <div class="main-content-full-width">
48 48 ${self.main_content()}
49 49 </div>
50 50 </div>
51 51 </div>
52 52
53 53 </%def> No newline at end of file
@@ -1,72 +1,72 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add user group')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10 <%def name="breadcrumbs_links()">
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 12 &raquo;
13 13 ${h.link_to(_('User groups'),h.url('users_groups'))}
14 14 &raquo;
15 15 ${_('Add User Group')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='admin')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box main-content">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28 <!-- end box / title -->
29 29 ${h.secure_form(url('users_groups'))}
30 30 <div class="form">
31 31 <!-- fields -->
32 32 <div class="fields">
33 33 <div class="field">
34 34 <div class="label">
35 35 <label for="users_group_name">${_('Group name')}:</label>
36 36 </div>
37 37 <div class="input">
38 38 ${h.text('users_group_name', class_='medium')}
39 39 </div>
40 40 </div>
41 41 <div class="field">
42 42 <div class="label">
43 43 <label for="user_group_description">${_('Description')}:</label>
44 44 </div>
45 45 <div class="textarea editor">
46 46 ${h.textarea('user_group_description')}
47 47 <span class="help-block">${_('Short, optional description for this user group.')}</span>
48 48 </div>
49 49 </div>
50 50 <div class="field">
51 51 <div class="label">
52 52 <label for="users_group_active">${_('Active')}:</label>
53 53 </div>
54 54 <div class="checkboxes">
55 55 ${h.checkbox('users_group_active',value=True, checked='checked')}
56 56 </div>
57 57 </div>
58 58
59 59 <div class="buttons">
60 60 ${h.submit('save',_('Save'),class_="btn")}
61 61 </div>
62 62 </div>
63 63 </div>
64 64 ${h.end_form()}
65 65 </div>
66 66 </%def>
67 67
68 68 <script>
69 69 $(document).ready(function(){
70 70 $('#users_group_name').focus();
71 71 })
72 72 </script>
@@ -1,46 +1,46 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s user group settings') % c.user_group.users_group_name}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
15 15 &raquo;
16 16 ${c.user_group.users_group_name}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='admin')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28
29 29 ##main
30 30 <div class="sidebar-col-wrapper">
31 31 <div class="sidebar">
32 32 <ul class="nav nav-pills nav-stacked">
33 33 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_users_group', user_group_id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
34 34 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_group_perms', user_group_id=c.user_group.users_group_id)}">${_('Permissions')}</a></li>
35 35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', user_group_id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
36 36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id)}">${_('Global permissions')}</a></li>
37 37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_group_perms_summary', user_group_id=c.user_group.users_group_id)}">${_('Permissions summary')}</a></li>
38 38 </ul>
39 39 </div>
40 40
41 41 <div class="main-content-full-width">
42 42 <%include file="/admin/user_groups/user_group_edit_${c.active}.mako"/>
43 43 </div>
44 44 </div>
45 45 </div>
46 46 </%def>
@@ -1,100 +1,100 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('User groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 <ul class="links">
26 26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 27 <li>
28 28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 29 </li>
30 30 %endif
31 31 </ul>
32 32 </div>
33 33
34 34 <div id="repos_list_wrap">
35 35 <table id="user_group_list_table" class="display"></table>
36 36 </div>
37 37
38 38 </div>
39 39 <script>
40 40 $(document).ready(function() {
41 41
42 42 var get_datatable_count = function(){
43 43 var api = $('#user_group_list_table').dataTable().api();
44 44 $('#user_group_count').text(api.page.info().recordsDisplay);
45 45 };
46 46
47 47 // user list
48 48 $('#user_group_list_table').DataTable({
49 49 data: ${c.data|n},
50 50 dom: 'rtp',
51 51 pageLength: ${c.visual.admin_grid_items},
52 52 order: [[ 0, "asc" ]],
53 53 columns: [
54 54 { data: {"_": "group_name",
55 55 "sort": "group_name_raw"}, title: "${_('Name')}", className: "td-componentname" },
56 56 { data: {"_": "desc",
57 57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
58 58 { data: {"_": "members",
59 59 "sort": "members",
60 60 "type": Number}, title: "${_('Members')}", className: "td-number" },
61 61 { data: {"_": "sync",
62 62 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
63 63 { data: {"_": "active",
64 64 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
65 65 { data: {"_": "owner",
66 66 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
67 67 { data: {"_": "action",
68 68 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
69 69 ],
70 70 language: {
71 71 paginate: DEFAULT_GRID_PAGINATION,
72 72 emptyTable: _gettext("No user groups available yet.")
73 73 },
74 74 "initComplete": function( settings, json ) {
75 75 get_datatable_count();
76 76 }
77 77 });
78 78
79 79 // update the counter when doing search
80 80 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
81 81 get_datatable_count();
82 82 });
83 83
84 84 // filter, filter both grids
85 85 $('#q_filter').on( 'keyup', function () {
86 86 var user_api = $('#user_group_list_table').dataTable().api();
87 87 user_api
88 88 .columns(0)
89 89 .search(this.value)
90 90 .draw();
91 91 });
92 92
93 93 // refilter table if page load via back button
94 94 $("#q_filter").trigger('keyup');
95 95
96 96 });
97 97
98 98 </script>
99 99
100 100 </%def>
@@ -1,147 +1,147 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add user')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10 <%def name="breadcrumbs_links()">
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 12 &raquo;
13 13 ${h.link_to(_('Users'),h.route_path('users'))}
14 14 &raquo;
15 15 ${_('Add User')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='admin')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28 <!-- end box / title -->
29 29 ${h.secure_form(url('users'))}
30 30 <div class="form">
31 31 <!-- fields -->
32 32 <div class="fields">
33 33 <div class="field">
34 34 <div class="label">
35 35 <label for="username">${_('Username')}:</label>
36 36 </div>
37 37 <div class="input">
38 38 ${h.text('username', class_='medium')}
39 39 </div>
40 40 </div>
41 41
42 42 <div class="field">
43 43 <div class="label">
44 44 <label for="password">${_('Password')}:</label>
45 45 </div>
46 46 <div class="input">
47 47 ${h.password('password', class_='medium')}
48 48 </div>
49 49 </div>
50 50
51 51 <div class="field">
52 52 <div class="label">
53 53 <label for="password_confirmation">${_('Password confirmation')}:</label>
54 54 </div>
55 55 <div class="input">
56 56 ${h.password('password_confirmation',autocomplete="off", class_='medium')}
57 57 <div class="info-block">
58 58 <a id="generate_password" href="#">
59 59 <i class="icon-lock"></i> ${_('Generate password')}
60 60 </a>
61 61 <span id="generate_password_preview"></span>
62 62 </div>
63 63 </div>
64 64 </div>
65 65
66 66 <div class="field">
67 67 <div class="label">
68 68 <label for="firstname">${_('First Name')}:</label>
69 69 </div>
70 70 <div class="input">
71 71 ${h.text('firstname', class_='medium')}
72 72 </div>
73 73 </div>
74 74
75 75 <div class="field">
76 76 <div class="label">
77 77 <label for="lastname">${_('Last Name')}:</label>
78 78 </div>
79 79 <div class="input">
80 80 ${h.text('lastname', class_='medium')}
81 81 </div>
82 82 </div>
83 83
84 84 <div class="field">
85 85 <div class="label">
86 86 <label for="email">${_('Email')}:</label>
87 87 </div>
88 88 <div class="input">
89 89 ${h.text('email', class_='medium')}
90 90 ${h.hidden('extern_name', c.default_extern_type)}
91 91 ${h.hidden('extern_type', c.default_extern_type)}
92 92 </div>
93 93 </div>
94 94
95 95 <div class="field">
96 96 <div class="label label-checkbox">
97 97 <label for="active">${_('Active')}:</label>
98 98 </div>
99 99 <div class="checkboxes">
100 100 ${h.checkbox('active',value=True,checked='checked')}
101 101 </div>
102 102 </div>
103 103
104 104 <div class="field">
105 105 <div class="label label-checkbox">
106 106 <label for="password_change">${_('Password change')}:</label>
107 107 </div>
108 108 <div class="checkboxes">
109 109 ${h.checkbox('password_change',value=True)}
110 110 <span class="help-block">${_('Force user to change his password on the next login')}</span>
111 111 </div>
112 112 </div>
113 113
114 114 <div class="field">
115 115 <div class="label label-checkbox">
116 116 <label for="create_repo_group">${_('Add personal repository group')}:</label>
117 117 </div>
118 118 <div class="checkboxes">
119 119 ${h.checkbox('create_repo_group',value=True, checked=c.default_create_repo_group)}
120 120 <span class="help-block">
121 121 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}<br/>
122 122 ${_('User will be automatically set as this group owner.')}
123 123 </span>
124 124 </div>
125 125 </div>
126 126
127 127 <div class="buttons">
128 128 ${h.submit('save',_('Save'),class_="btn")}
129 129 </div>
130 130 </div>
131 131 </div>
132 132 ${h.end_form()}
133 133 </div>
134 134 <script>
135 135 $(document).ready(function(){
136 136 $('#username').focus();
137 137
138 138 $('#generate_password').on('click', function(e){
139 139 var tmpl = "(${_('generated password:')} {0})";
140 140 var new_passwd = generatePassword(12);
141 141 $('#generate_password_preview').html(tmpl.format(new_passwd));
142 142 $('#password').val(new_passwd);
143 143 $('#password_confirmation').val(new_passwd);
144 144 })
145 145 })
146 146 </script>
147 147 </%def>
@@ -1,56 +1,56 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s user settings') % c.user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Users'),h.route_path('users'))}
15 15 &raquo;
16 16 % if c.user.active:
17 17 ${c.user.username}
18 18 % else:
19 19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
20 20 % endif
21 21
22 22 </%def>
23 23
24 24 <%def name="menu_bar_nav()">
25 25 ${self.menu_items(active='admin')}
26 26 </%def>
27 27
28 28 <%def name="main()">
29 29 <div class="box user_settings">
30 30 <div class="title">
31 31 ${self.breadcrumbs()}
32 32 </div>
33 33
34 34 ##main
35 35 <div class="sidebar-col-wrapper">
36 36 <div class="sidebar">
37 37 <ul class="nav nav-pills nav-stacked">
38 38 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
39 39 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
40 40 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
41 41 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
42 42 <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>
43 43 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
44 44 <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>
45 45 <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>
46 46 <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>
47 47 </ul>
48 48 </div>
49 49
50 50 <div class="main-content-full-width">
51 51 <%include file="/admin/users/user_edit_${c.active}.mako"/>
52 52 </div>
53 53 </div>
54 54 </div>
55 55
56 56 </%def>
@@ -1,120 +1,120 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Users administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_count">0</span>
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21
22 22 <div class="box">
23 23
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 <ul class="links">
27 27 <li>
28 28 <a href="${h.url('new_user')}" class="btn btn-small btn-success">${_(u'Add User')}</a>
29 29 </li>
30 30 </ul>
31 31 </div>
32 32
33 33 <div id="repos_list_wrap">
34 34 <table id="user_list_table" class="display"></table>
35 35 </div>
36 36 </div>
37 37
38 38 <script type="text/javascript">
39 39
40 40 $(document).ready(function() {
41 41 var $userListTable = $('#user_list_table');
42 42
43 43 var getDatatableCount = function(){
44 44 var table = $userListTable.dataTable();
45 45 var page = table.api().page.info();
46 46 var active = page.recordsDisplay;
47 47 var total = page.recordsTotal;
48 48
49 49 var _text = _gettext("{0} out of {1} users").format(active, total);
50 50 $('#user_count').text(_text);
51 51 };
52 52
53 53 // user list
54 54 $userListTable.DataTable({
55 55 processing: true,
56 56 serverSide: true,
57 57 ajax: "${h.route_path('users_data')}",
58 58 dom: 'rtp',
59 59 pageLength: ${c.visual.admin_grid_items},
60 60 order: [[ 0, "asc" ]],
61 61 columns: [
62 62 { data: {"_": "username",
63 63 "sort": "username"}, title: "${_('Username')}", className: "td-user" },
64 64 { data: {"_": "email",
65 65 "sort": "email"}, title: "${_('Email')}", className: "td-email" },
66 66 { data: {"_": "first_name",
67 67 "sort": "first_name"}, title: "${_('First Name')}", className: "td-user" },
68 68 { data: {"_": "last_name",
69 69 "sort": "last_name"}, title: "${_('Last Name')}", className: "td-user" },
70 70 { data: {"_": "last_activity",
71 71 "sort": "last_activity",
72 72 "type": Number}, title: "${_('Last activity')}", className: "td-time" },
73 73 { data: {"_": "active",
74 74 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
75 75 { data: {"_": "admin",
76 76 "sort": "admin"}, title: "${_('Admin')}", className: "td-admin" },
77 77 { data: {"_": "extern_type",
78 78 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
79 79 { data: {"_": "action",
80 80 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
81 81 ],
82 82 language: {
83 83 paginate: DEFAULT_GRID_PAGINATION,
84 84 sProcessing: _gettext('loading...'),
85 85 emptyTable: _gettext("No users available yet.")
86 86 },
87 87
88 88 "createdRow": function ( row, data, index ) {
89 89 if (!data['active_raw']){
90 90 $(row).addClass('closed')
91 91 }
92 92 }
93 93 });
94 94
95 95 $userListTable.on('xhr.dt', function(e, settings, json, xhr){
96 96 $userListTable.css('opacity', 1);
97 97 });
98 98
99 99 $userListTable.on('preXhr.dt', function(e, settings, data){
100 100 $userListTable.css('opacity', 0.3);
101 101 });
102 102
103 103 // refresh counters on draw
104 104 $userListTable.on('draw.dt', function(){
105 105 getDatatableCount();
106 106 });
107 107
108 108 // filter
109 109 $('#q_filter').on('keyup',
110 110 $.debounce(250, function() {
111 111 $userListTable.DataTable().search(
112 112 $('#q_filter').val()
113 113 ).draw();
114 114 })
115 115 );
116 116
117 117 });
118 118 </script>
119 119
120 120 </%def>
@@ -1,602 +1,602 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${title}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.author_string(email)}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 236 <li class="${is_active('showpullrequest')}">
237 237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
241 241 <div class="menulabel">${_('Pull Requests')}</div>
242 242 </a>
243 243 </li>
244 244 %endif
245 245 <li class="${is_active('options')}">
246 246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 247 <ul class="submenu">
248 248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 250 %endif
251 251 %if c.rhodecode_db_repo.fork:
252 252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 253 ${_('Compare fork')}</a></li>
254 254 %endif
255 255
256 256 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
257 257
258 258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 259 %if c.rhodecode_db_repo.locked[0]:
260 260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 261 %else:
262 262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 263 %endif
264 264 %endif
265 265 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 269 %endif
270 270 %endif
271 271 </ul>
272 272 </li>
273 273 </ul>
274 274 </div>
275 275 <div class="clear"></div>
276 276 </div>
277 277 <!--- END CONTEXT BAR -->
278 278
279 279 </%def>
280 280
281 281 <%def name="usermenu(active=False)">
282 282 ## USER MENU
283 283 <li id="quick_login_li" class="${'active' if active else ''}">
284 284 <a id="quick_login_link" class="menulink childs">
285 285 ${gravatar(c.rhodecode_user.email, 20)}
286 286 <span class="user">
287 287 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 289 %else:
290 290 <span>${_('Sign in')}</span>
291 291 %endif
292 292 </span>
293 293 </a>
294 294
295 295 <div class="user-menu submenu">
296 296 <div id="quick_login">
297 297 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 298 <h4>${_('Sign in to your account')}</h4>
299 299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 300 <div class="form form-vertical">
301 301 <div class="fields">
302 302 <div class="field">
303 303 <div class="label">
304 304 <label for="username">${_('Username')}:</label>
305 305 </div>
306 306 <div class="input">
307 307 ${h.text('username',class_='focus',tabindex=1)}
308 308 </div>
309 309
310 310 </div>
311 311 <div class="field">
312 312 <div class="label">
313 313 <label for="password">${_('Password')}:</label>
314 314 %if h.HasPermissionAny('hg.password_reset.enabled')():
315 315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
316 316 %endif
317 317 </div>
318 318 <div class="input">
319 319 ${h.password('password',class_='focus',tabindex=2)}
320 320 </div>
321 321 </div>
322 322 <div class="buttons">
323 323 <div class="register">
324 324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
325 325 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
326 326 %endif
327 327 ${h.link_to(_("Using external auth? Login here."),h.route_path('login'))}
328 328 </div>
329 329 <div class="submit">
330 330 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
331 331 </div>
332 332 </div>
333 333 </div>
334 334 </div>
335 335 ${h.end_form()}
336 336 %else:
337 337 <div class="">
338 338 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
339 339 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
340 340 <div class="email">${c.rhodecode_user.email}</div>
341 341 </div>
342 342 <div class="">
343 343 <ol class="links">
344 344 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
345 345 % if c.rhodecode_user.personal_repo_group:
346 346 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
347 347 % endif
348 348 <li class="logout">
349 349 ${h.secure_form(h.route_path('logout'))}
350 350 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
351 351 ${h.end_form()}
352 352 </li>
353 353 </ol>
354 354 </div>
355 355 %endif
356 356 </div>
357 357 </div>
358 358 %if c.rhodecode_user.username != h.DEFAULT_USER:
359 359 <div class="pill_container">
360 360 % if c.unread_notifications == 0:
361 361 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
362 362 % else:
363 363 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
364 364 % endif
365 365 </div>
366 366 % endif
367 367 </li>
368 368 </%def>
369 369
370 370 <%def name="menu_items(active=None)">
371 371 <%
372 372 def is_active(selected):
373 373 if selected == active:
374 374 return "active"
375 375 return ""
376 376 %>
377 377 <ul id="quick" class="main_nav navigation horizontal-list">
378 378 <!-- repo switcher -->
379 379 <li class="${is_active('repositories')} repo_switcher_li has_select2">
380 380 <input id="repo_switcher" name="repo_switcher" type="hidden">
381 381 </li>
382 382
383 383 ## ROOT MENU
384 384 %if c.rhodecode_user.username != h.DEFAULT_USER:
385 385 <li class="${is_active('journal')}">
386 386 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
387 387 <div class="menulabel">${_('Journal')}</div>
388 388 </a>
389 389 </li>
390 390 %else:
391 391 <li class="${is_active('journal')}">
392 392 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
393 393 <div class="menulabel">${_('Public journal')}</div>
394 394 </a>
395 395 </li>
396 396 %endif
397 397 <li class="${is_active('gists')}">
398 398 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
399 399 <div class="menulabel">${_('Gists')}</div>
400 400 </a>
401 401 </li>
402 402 <li class="${is_active('search')}">
403 403 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
404 404 <div class="menulabel">${_('Search')}</div>
405 405 </a>
406 406 </li>
407 407 % if h.HasPermissionAll('hg.admin')('access admin main page'):
408 408 <li class="${is_active('admin')}">
409 409 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
410 410 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
411 411 </a>
412 412 ${admin_menu()}
413 413 </li>
414 414 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
415 415 <li class="${is_active('admin')}">
416 416 <a class="menulink childs" title="${_('Delegated Admin settings')}">
417 417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
418 418 </a>
419 419 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
420 420 c.rhodecode_user.repository_groups_admin,
421 421 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
422 422 </li>
423 423 % endif
424 424 % if c.debug_style:
425 425 <li class="${is_active('debug_style')}">
426 426 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
427 427 <div class="menulabel">${_('Style')}</div>
428 428 </a>
429 429 </li>
430 430 % endif
431 431 ## render extra user menu
432 432 ${usermenu(active=(active=='my_account'))}
433 433 </ul>
434 434
435 435 <script type="text/javascript">
436 436 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
437 437
438 438 /*format the look of items in the list*/
439 439 var format = function(state, escapeMarkup){
440 440 if (!state.id){
441 441 return state.text; // optgroup
442 442 }
443 443 var obj_dict = state.obj;
444 444 var tmpl = '';
445 445
446 446 if(obj_dict && state.type == 'repo'){
447 447 if(obj_dict['repo_type'] === 'hg'){
448 448 tmpl += '<i class="icon-hg"></i> ';
449 449 }
450 450 else if(obj_dict['repo_type'] === 'git'){
451 451 tmpl += '<i class="icon-git"></i> ';
452 452 }
453 453 else if(obj_dict['repo_type'] === 'svn'){
454 454 tmpl += '<i class="icon-svn"></i> ';
455 455 }
456 456 if(obj_dict['private']){
457 457 tmpl += '<i class="icon-lock" ></i> ';
458 458 }
459 459 else if(visual_show_public_icon){
460 460 tmpl += '<i class="icon-unlock-alt"></i> ';
461 461 }
462 462 }
463 463 if(obj_dict && state.type == 'commit') {
464 464 tmpl += '<i class="icon-tag"></i>';
465 465 }
466 466 if(obj_dict && state.type == 'group'){
467 467 tmpl += '<i class="icon-folder-close"></i> ';
468 468 }
469 469 tmpl += escapeMarkup(state.text);
470 470 return tmpl;
471 471 };
472 472
473 473 var formatResult = function(result, container, query, escapeMarkup) {
474 474 return format(result, escapeMarkup);
475 475 };
476 476
477 477 var formatSelection = function(data, container, escapeMarkup) {
478 478 return format(data, escapeMarkup);
479 479 };
480 480
481 481 $("#repo_switcher").select2({
482 482 cachedDataSource: {},
483 483 minimumInputLength: 2,
484 484 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
485 485 dropdownAutoWidth: true,
486 486 formatResult: formatResult,
487 487 formatSelection: formatSelection,
488 488 containerCssClass: "repo-switcher",
489 489 dropdownCssClass: "repo-switcher-dropdown",
490 490 escapeMarkup: function(m){
491 491 // don't escape our custom placeholder
492 492 if(m.substr(0,23) == '<div class="menulabel">'){
493 493 return m;
494 494 }
495 495
496 496 return Select2.util.escapeMarkup(m);
497 497 },
498 498 query: $.debounce(250, function(query){
499 499 self = this;
500 500 var cacheKey = query.term;
501 501 var cachedData = self.cachedDataSource[cacheKey];
502 502
503 503 if (cachedData) {
504 504 query.callback({results: cachedData.results});
505 505 } else {
506 506 $.ajax({
507 507 url: pyroutes.url('goto_switcher_data'),
508 508 data: {'query': query.term},
509 509 dataType: 'json',
510 510 type: 'GET',
511 511 success: function(data) {
512 512 self.cachedDataSource[cacheKey] = data;
513 513 query.callback({results: data.results});
514 514 },
515 515 error: function(data, textStatus, errorThrown) {
516 516 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
517 517 }
518 518 })
519 519 }
520 520 })
521 521 });
522 522
523 523 $("#repo_switcher").on('select2-selecting', function(e){
524 524 e.preventDefault();
525 525 window.location = e.choice.url;
526 526 });
527 527
528 528 </script>
529 529 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
530 530 </%def>
531 531
532 532 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
533 533 <div class="modal-dialog">
534 534 <div class="modal-content">
535 535 <div class="modal-header">
536 536 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
537 537 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
538 538 </div>
539 539 <div class="modal-body">
540 540 <div class="block-left">
541 541 <table class="keyboard-mappings">
542 542 <tbody>
543 543 <tr>
544 544 <th></th>
545 545 <th>${_('Site-wide shortcuts')}</th>
546 546 </tr>
547 547 <%
548 548 elems = [
549 549 ('/', 'Open quick search box'),
550 550 ('g h', 'Goto home page'),
551 551 ('g g', 'Goto my private gists page'),
552 552 ('g G', 'Goto my public gists page'),
553 553 ('n r', 'New repository page'),
554 554 ('n g', 'New gist page'),
555 555 ]
556 556 %>
557 557 %for key, desc in elems:
558 558 <tr>
559 559 <td class="keys">
560 560 <span class="key tag">${key}</span>
561 561 </td>
562 562 <td>${desc}</td>
563 563 </tr>
564 564 %endfor
565 565 </tbody>
566 566 </table>
567 567 </div>
568 568 <div class="block-left">
569 569 <table class="keyboard-mappings">
570 570 <tbody>
571 571 <tr>
572 572 <th></th>
573 573 <th>${_('Repositories')}</th>
574 574 </tr>
575 575 <%
576 576 elems = [
577 577 ('g s', 'Goto summary page'),
578 578 ('g c', 'Goto changelog page'),
579 579 ('g f', 'Goto files page'),
580 580 ('g F', 'Goto files page with file search activated'),
581 581 ('g p', 'Goto pull requests page'),
582 582 ('g o', 'Goto repository settings'),
583 583 ('g O', 'Goto repository permissions settings'),
584 584 ]
585 585 %>
586 586 %for key, desc in elems:
587 587 <tr>
588 588 <td class="keys">
589 589 <span class="key tag">${key}</span>
590 590 </td>
591 591 <td>${desc}</td>
592 592 </tr>
593 593 %endfor
594 594 </tbody>
595 595 </table>
596 596 </div>
597 597 </div>
598 598 <div class="modal-footer">
599 599 </div>
600 600 </div><!-- /.modal-content -->
601 601 </div><!-- /.modal-dialog -->
602 602 </div><!-- /.modal -->
@@ -1,1103 +1,1090 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23 from webob.exc import HTTPNotFound
24 24
25 25 import rhodecode
26 26 from rhodecode.lib.vcs.nodes import FileNode
27 27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 28 from rhodecode.model.db import (
29 29 PullRequest, ChangesetStatus, UserLog, Notification)
30 30 from rhodecode.model.meta import Session
31 31 from rhodecode.model.pull_request import PullRequestModel
32 32 from rhodecode.model.user import UserModel
33 33 from rhodecode.model.repo import RepoModel
34 34 from rhodecode.tests import (
35 35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 36 from rhodecode.tests.utils import AssertResponse
37 37
38 38
39 39 @pytest.mark.usefixtures('app', 'autologin_user')
40 40 @pytest.mark.backends("git", "hg")
41 41 class TestPullrequestsController:
42 42
43 43 def test_index(self, backend):
44 44 self.app.get(url(
45 45 controller='pullrequests', action='index',
46 46 repo_name=backend.repo_name))
47 47
48 48 def test_option_menu_create_pull_request_exists(self, backend):
49 49 repo_name = backend.repo_name
50 50 response = self.app.get(url('summary_home', repo_name=repo_name))
51 51
52 52 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
53 53 'pullrequest', repo_name=repo_name)
54 54 response.mustcontain(create_pr_link)
55 55
56 def test_global_redirect_of_pr(self, backend, pr_util):
57 pull_request = pr_util.create_pull_request()
58
59 response = self.app.get(
60 url('pull_requests_global',
61 pull_request_id=pull_request.pull_request_id))
62
63 repo_name = pull_request.target_repo.repo_name
64 redirect_url = url('pullrequest_show', repo_name=repo_name,
65 pull_request_id=pull_request.pull_request_id)
66 assert response.status == '302 Found'
67 assert redirect_url in response.location
68
69 56 def test_create_pr_form_with_raw_commit_id(self, backend):
70 57 repo = backend.repo
71 58
72 59 self.app.get(
73 60 url(controller='pullrequests', action='index',
74 61 repo_name=repo.repo_name,
75 62 commit=repo.get_commit().raw_id),
76 63 status=200)
77 64
78 65 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
79 66 def test_show(self, pr_util, pr_merge_enabled):
80 67 pull_request = pr_util.create_pull_request(
81 68 mergeable=pr_merge_enabled, enable_notifications=False)
82 69
83 70 response = self.app.get(url(
84 71 controller='pullrequests', action='show',
85 72 repo_name=pull_request.target_repo.scm_instance().name,
86 73 pull_request_id=str(pull_request.pull_request_id)))
87 74
88 75 for commit_id in pull_request.revisions:
89 76 response.mustcontain(commit_id)
90 77
91 78 assert pull_request.target_ref_parts.type in response
92 79 assert pull_request.target_ref_parts.name in response
93 80 target_clone_url = pull_request.target_repo.clone_url()
94 81 assert target_clone_url in response
95 82
96 83 assert 'class="pull-request-merge"' in response
97 84 assert (
98 85 'Server-side pull request merging is disabled.'
99 86 in response) != pr_merge_enabled
100 87
101 88 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
102 89 from rhodecode.tests.functional.test_login import login_url, logut_url
103 90 # Logout
104 91 response = self.app.post(
105 92 logut_url,
106 93 params={'csrf_token': csrf_token})
107 94 # Login as regular user
108 95 response = self.app.post(login_url,
109 96 {'username': TEST_USER_REGULAR_LOGIN,
110 97 'password': 'test12'})
111 98
112 99 pull_request = pr_util.create_pull_request(
113 100 author=TEST_USER_REGULAR_LOGIN)
114 101
115 102 response = self.app.get(url(
116 103 controller='pullrequests', action='show',
117 104 repo_name=pull_request.target_repo.scm_instance().name,
118 105 pull_request_id=str(pull_request.pull_request_id)))
119 106
120 107 response.mustcontain('Server-side pull request merging is disabled.')
121 108
122 109 assert_response = response.assert_response()
123 110 # for regular user without a merge permissions, we don't see it
124 111 assert_response.no_element_exists('#close-pull-request-action')
125 112
126 113 user_util.grant_user_permission_to_repo(
127 114 pull_request.target_repo,
128 115 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
129 116 'repository.write')
130 117 response = self.app.get(url(
131 118 controller='pullrequests', action='show',
132 119 repo_name=pull_request.target_repo.scm_instance().name,
133 120 pull_request_id=str(pull_request.pull_request_id)))
134 121
135 122 response.mustcontain('Server-side pull request merging is disabled.')
136 123
137 124 assert_response = response.assert_response()
138 125 # now regular user has a merge permissions, we have CLOSE button
139 126 assert_response.one_element_exists('#close-pull-request-action')
140 127
141 128 def test_show_invalid_commit_id(self, pr_util):
142 129 # Simulating invalid revisions which will cause a lookup error
143 130 pull_request = pr_util.create_pull_request()
144 131 pull_request.revisions = ['invalid']
145 132 Session().add(pull_request)
146 133 Session().commit()
147 134
148 135 response = self.app.get(url(
149 136 controller='pullrequests', action='show',
150 137 repo_name=pull_request.target_repo.scm_instance().name,
151 138 pull_request_id=str(pull_request.pull_request_id)))
152 139
153 140 for commit_id in pull_request.revisions:
154 141 response.mustcontain(commit_id)
155 142
156 143 def test_show_invalid_source_reference(self, pr_util):
157 144 pull_request = pr_util.create_pull_request()
158 145 pull_request.source_ref = 'branch:b:invalid'
159 146 Session().add(pull_request)
160 147 Session().commit()
161 148
162 149 self.app.get(url(
163 150 controller='pullrequests', action='show',
164 151 repo_name=pull_request.target_repo.scm_instance().name,
165 152 pull_request_id=str(pull_request.pull_request_id)))
166 153
167 154 def test_edit_title_description(self, pr_util, csrf_token):
168 155 pull_request = pr_util.create_pull_request()
169 156 pull_request_id = pull_request.pull_request_id
170 157
171 158 response = self.app.post(
172 159 url(controller='pullrequests', action='update',
173 160 repo_name=pull_request.target_repo.repo_name,
174 161 pull_request_id=str(pull_request_id)),
175 162 params={
176 163 'edit_pull_request': 'true',
177 164 '_method': 'put',
178 165 'title': 'New title',
179 166 'description': 'New description',
180 167 'csrf_token': csrf_token})
181 168
182 169 assert_session_flash(
183 170 response, u'Pull request title & description updated.',
184 171 category='success')
185 172
186 173 pull_request = PullRequest.get(pull_request_id)
187 174 assert pull_request.title == 'New title'
188 175 assert pull_request.description == 'New description'
189 176
190 177 def test_edit_title_description_closed(self, pr_util, csrf_token):
191 178 pull_request = pr_util.create_pull_request()
192 179 pull_request_id = pull_request.pull_request_id
193 180 pr_util.close()
194 181
195 182 response = self.app.post(
196 183 url(controller='pullrequests', action='update',
197 184 repo_name=pull_request.target_repo.repo_name,
198 185 pull_request_id=str(pull_request_id)),
199 186 params={
200 187 'edit_pull_request': 'true',
201 188 '_method': 'put',
202 189 'title': 'New title',
203 190 'description': 'New description',
204 191 'csrf_token': csrf_token})
205 192
206 193 assert_session_flash(
207 194 response, u'Cannot update closed pull requests.',
208 195 category='error')
209 196
210 197 def test_update_invalid_source_reference(self, pr_util, csrf_token):
211 198 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
212 199
213 200 pull_request = pr_util.create_pull_request()
214 201 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
215 202 Session().add(pull_request)
216 203 Session().commit()
217 204
218 205 pull_request_id = pull_request.pull_request_id
219 206
220 207 response = self.app.post(
221 208 url(controller='pullrequests', action='update',
222 209 repo_name=pull_request.target_repo.repo_name,
223 210 pull_request_id=str(pull_request_id)),
224 211 params={'update_commits': 'true', '_method': 'put',
225 212 'csrf_token': csrf_token})
226 213
227 214 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
228 215 UpdateFailureReason.MISSING_SOURCE_REF]
229 216 assert_session_flash(response, expected_msg, category='error')
230 217
231 218 def test_missing_target_reference(self, pr_util, csrf_token):
232 219 from rhodecode.lib.vcs.backends.base import MergeFailureReason
233 220 pull_request = pr_util.create_pull_request(
234 221 approved=True, mergeable=True)
235 222 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
236 223 Session().add(pull_request)
237 224 Session().commit()
238 225
239 226 pull_request_id = pull_request.pull_request_id
240 227 pull_request_url = url(
241 228 controller='pullrequests', action='show',
242 229 repo_name=pull_request.target_repo.repo_name,
243 230 pull_request_id=str(pull_request_id))
244 231
245 232 response = self.app.get(pull_request_url)
246 233
247 234 assertr = AssertResponse(response)
248 235 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
249 236 MergeFailureReason.MISSING_TARGET_REF]
250 237 assertr.element_contains(
251 238 'span[data-role="merge-message"]', str(expected_msg))
252 239
253 240 def test_comment_and_close_pull_request(self, pr_util, csrf_token):
254 241 pull_request = pr_util.create_pull_request(approved=True)
255 242 pull_request_id = pull_request.pull_request_id
256 243 author = pull_request.user_id
257 244 repo = pull_request.target_repo.repo_id
258 245
259 246 self.app.post(
260 247 url(controller='pullrequests',
261 248 action='comment',
262 249 repo_name=pull_request.target_repo.scm_instance().name,
263 250 pull_request_id=str(pull_request_id)),
264 251 params={
265 252 'changeset_status': ChangesetStatus.STATUS_APPROVED,
266 253 'close_pull_request': '1',
267 254 'text': 'Closing a PR',
268 255 'csrf_token': csrf_token},
269 256 status=302)
270 257
271 258 action = 'user_closed_pull_request:%d' % pull_request_id
272 259 journal = UserLog.query()\
273 260 .filter(UserLog.user_id == author)\
274 261 .filter(UserLog.repository_id == repo)\
275 262 .filter(UserLog.action == action)\
276 263 .all()
277 264 assert len(journal) == 1
278 265
279 266 pull_request = PullRequest.get(pull_request_id)
280 267 assert pull_request.is_closed()
281 268
282 269 # check only the latest status, not the review status
283 270 status = ChangesetStatusModel().get_status(
284 271 pull_request.source_repo, pull_request=pull_request)
285 272 assert status == ChangesetStatus.STATUS_APPROVED
286 273
287 274 def test_reject_and_close_pull_request(self, pr_util, csrf_token):
288 275 pull_request = pr_util.create_pull_request()
289 276 pull_request_id = pull_request.pull_request_id
290 277 response = self.app.post(
291 278 url(controller='pullrequests',
292 279 action='update',
293 280 repo_name=pull_request.target_repo.scm_instance().name,
294 281 pull_request_id=str(pull_request.pull_request_id)),
295 282 params={'close_pull_request': 'true', '_method': 'put',
296 283 'csrf_token': csrf_token})
297 284
298 285 pull_request = PullRequest.get(pull_request_id)
299 286
300 287 assert response.json is True
301 288 assert pull_request.is_closed()
302 289
303 290 # check only the latest status, not the review status
304 291 status = ChangesetStatusModel().get_status(
305 292 pull_request.source_repo, pull_request=pull_request)
306 293 assert status == ChangesetStatus.STATUS_REJECTED
307 294
308 295 def test_comment_force_close_pull_request(self, pr_util, csrf_token):
309 296 pull_request = pr_util.create_pull_request()
310 297 pull_request_id = pull_request.pull_request_id
311 298 reviewers_data = [(1, ['reason']), (2, ['reason2'])]
312 299 PullRequestModel().update_reviewers(pull_request_id, reviewers_data)
313 300 author = pull_request.user_id
314 301 repo = pull_request.target_repo.repo_id
315 302 self.app.post(
316 303 url(controller='pullrequests',
317 304 action='comment',
318 305 repo_name=pull_request.target_repo.scm_instance().name,
319 306 pull_request_id=str(pull_request_id)),
320 307 params={
321 308 'changeset_status': 'rejected',
322 309 'close_pull_request': '1',
323 310 'csrf_token': csrf_token},
324 311 status=302)
325 312
326 313 pull_request = PullRequest.get(pull_request_id)
327 314
328 315 action = 'user_closed_pull_request:%d' % pull_request_id
329 316 journal = UserLog.query().filter(
330 317 UserLog.user_id == author,
331 318 UserLog.repository_id == repo,
332 319 UserLog.action == action).all()
333 320 assert len(journal) == 1
334 321
335 322 # check only the latest status, not the review status
336 323 status = ChangesetStatusModel().get_status(
337 324 pull_request.source_repo, pull_request=pull_request)
338 325 assert status == ChangesetStatus.STATUS_REJECTED
339 326
340 327 def test_create_pull_request(self, backend, csrf_token):
341 328 commits = [
342 329 {'message': 'ancestor'},
343 330 {'message': 'change'},
344 331 {'message': 'change2'},
345 332 ]
346 333 commit_ids = backend.create_master_repo(commits)
347 334 target = backend.create_repo(heads=['ancestor'])
348 335 source = backend.create_repo(heads=['change2'])
349 336
350 337 response = self.app.post(
351 338 url(
352 339 controller='pullrequests',
353 340 action='create',
354 341 repo_name=source.repo_name
355 342 ),
356 343 [
357 344 ('source_repo', source.repo_name),
358 345 ('source_ref', 'branch:default:' + commit_ids['change2']),
359 346 ('target_repo', target.repo_name),
360 347 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
361 348 ('pullrequest_desc', 'Description'),
362 349 ('pullrequest_title', 'Title'),
363 350 ('__start__', 'review_members:sequence'),
364 351 ('__start__', 'reviewer:mapping'),
365 352 ('user_id', '1'),
366 353 ('__start__', 'reasons:sequence'),
367 354 ('reason', 'Some reason'),
368 355 ('__end__', 'reasons:sequence'),
369 356 ('__end__', 'reviewer:mapping'),
370 357 ('__end__', 'review_members:sequence'),
371 358 ('__start__', 'revisions:sequence'),
372 359 ('revisions', commit_ids['change']),
373 360 ('revisions', commit_ids['change2']),
374 361 ('__end__', 'revisions:sequence'),
375 362 ('user', ''),
376 363 ('csrf_token', csrf_token),
377 364 ],
378 365 status=302)
379 366
380 367 location = response.headers['Location']
381 368 pull_request_id = int(location.rsplit('/', 1)[1])
382 369 pull_request = PullRequest.get(pull_request_id)
383 370
384 371 # check that we have now both revisions
385 372 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
386 373 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
387 374 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
388 375 assert pull_request.target_ref == expected_target_ref
389 376
390 377 def test_reviewer_notifications(self, backend, csrf_token):
391 378 # We have to use the app.post for this test so it will create the
392 379 # notifications properly with the new PR
393 380 commits = [
394 381 {'message': 'ancestor',
395 382 'added': [FileNode('file_A', content='content_of_ancestor')]},
396 383 {'message': 'change',
397 384 'added': [FileNode('file_a', content='content_of_change')]},
398 385 {'message': 'change-child'},
399 386 {'message': 'ancestor-child', 'parents': ['ancestor'],
400 387 'added': [
401 388 FileNode('file_B', content='content_of_ancestor_child')]},
402 389 {'message': 'ancestor-child-2'},
403 390 ]
404 391 commit_ids = backend.create_master_repo(commits)
405 392 target = backend.create_repo(heads=['ancestor-child'])
406 393 source = backend.create_repo(heads=['change'])
407 394
408 395 response = self.app.post(
409 396 url(
410 397 controller='pullrequests',
411 398 action='create',
412 399 repo_name=source.repo_name
413 400 ),
414 401 [
415 402 ('source_repo', source.repo_name),
416 403 ('source_ref', 'branch:default:' + commit_ids['change']),
417 404 ('target_repo', target.repo_name),
418 405 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
419 406 ('pullrequest_desc', 'Description'),
420 407 ('pullrequest_title', 'Title'),
421 408 ('__start__', 'review_members:sequence'),
422 409 ('__start__', 'reviewer:mapping'),
423 410 ('user_id', '2'),
424 411 ('__start__', 'reasons:sequence'),
425 412 ('reason', 'Some reason'),
426 413 ('__end__', 'reasons:sequence'),
427 414 ('__end__', 'reviewer:mapping'),
428 415 ('__end__', 'review_members:sequence'),
429 416 ('__start__', 'revisions:sequence'),
430 417 ('revisions', commit_ids['change']),
431 418 ('__end__', 'revisions:sequence'),
432 419 ('user', ''),
433 420 ('csrf_token', csrf_token),
434 421 ],
435 422 status=302)
436 423
437 424 location = response.headers['Location']
438 425 pull_request_id = int(location.rsplit('/', 1)[1])
439 426 pull_request = PullRequest.get(pull_request_id)
440 427
441 428 # Check that a notification was made
442 429 notifications = Notification.query()\
443 430 .filter(Notification.created_by == pull_request.author.user_id,
444 431 Notification.type_ == Notification.TYPE_PULL_REQUEST,
445 432 Notification.subject.contains("wants you to review "
446 433 "pull request #%d"
447 434 % pull_request_id))
448 435 assert len(notifications.all()) == 1
449 436
450 437 # Change reviewers and check that a notification was made
451 438 PullRequestModel().update_reviewers(
452 439 pull_request.pull_request_id, [(1, [])])
453 440 assert len(notifications.all()) == 2
454 441
455 442 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
456 443 csrf_token):
457 444 commits = [
458 445 {'message': 'ancestor',
459 446 'added': [FileNode('file_A', content='content_of_ancestor')]},
460 447 {'message': 'change',
461 448 'added': [FileNode('file_a', content='content_of_change')]},
462 449 {'message': 'change-child'},
463 450 {'message': 'ancestor-child', 'parents': ['ancestor'],
464 451 'added': [
465 452 FileNode('file_B', content='content_of_ancestor_child')]},
466 453 {'message': 'ancestor-child-2'},
467 454 ]
468 455 commit_ids = backend.create_master_repo(commits)
469 456 target = backend.create_repo(heads=['ancestor-child'])
470 457 source = backend.create_repo(heads=['change'])
471 458
472 459 response = self.app.post(
473 460 url(
474 461 controller='pullrequests',
475 462 action='create',
476 463 repo_name=source.repo_name
477 464 ),
478 465 [
479 466 ('source_repo', source.repo_name),
480 467 ('source_ref', 'branch:default:' + commit_ids['change']),
481 468 ('target_repo', target.repo_name),
482 469 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
483 470 ('pullrequest_desc', 'Description'),
484 471 ('pullrequest_title', 'Title'),
485 472 ('__start__', 'review_members:sequence'),
486 473 ('__start__', 'reviewer:mapping'),
487 474 ('user_id', '1'),
488 475 ('__start__', 'reasons:sequence'),
489 476 ('reason', 'Some reason'),
490 477 ('__end__', 'reasons:sequence'),
491 478 ('__end__', 'reviewer:mapping'),
492 479 ('__end__', 'review_members:sequence'),
493 480 ('__start__', 'revisions:sequence'),
494 481 ('revisions', commit_ids['change']),
495 482 ('__end__', 'revisions:sequence'),
496 483 ('user', ''),
497 484 ('csrf_token', csrf_token),
498 485 ],
499 486 status=302)
500 487
501 488 location = response.headers['Location']
502 489 pull_request_id = int(location.rsplit('/', 1)[1])
503 490 pull_request = PullRequest.get(pull_request_id)
504 491
505 492 # target_ref has to point to the ancestor's commit_id in order to
506 493 # show the correct diff
507 494 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
508 495 assert pull_request.target_ref == expected_target_ref
509 496
510 497 # Check generated diff contents
511 498 response = response.follow()
512 499 assert 'content_of_ancestor' not in response.body
513 500 assert 'content_of_ancestor-child' not in response.body
514 501 assert 'content_of_change' in response.body
515 502
516 503 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
517 504 # Clear any previous calls to rcextensions
518 505 rhodecode.EXTENSIONS.calls.clear()
519 506
520 507 pull_request = pr_util.create_pull_request(
521 508 approved=True, mergeable=True)
522 509 pull_request_id = pull_request.pull_request_id
523 510 repo_name = pull_request.target_repo.scm_instance().name,
524 511
525 512 response = self.app.post(
526 513 url(controller='pullrequests',
527 514 action='merge',
528 515 repo_name=str(repo_name[0]),
529 516 pull_request_id=str(pull_request_id)),
530 517 params={'csrf_token': csrf_token}).follow()
531 518
532 519 pull_request = PullRequest.get(pull_request_id)
533 520
534 521 assert response.status_int == 200
535 522 assert pull_request.is_closed()
536 523 assert_pull_request_status(
537 524 pull_request, ChangesetStatus.STATUS_APPROVED)
538 525
539 526 # Check the relevant log entries were added
540 527 user_logs = UserLog.query() \
541 528 .filter(UserLog.version == UserLog.VERSION_1) \
542 529 .order_by('-user_log_id').limit(3)
543 530 actions = [log.action for log in user_logs]
544 531 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
545 532 expected_actions = [
546 533 u'user_closed_pull_request:%d' % pull_request_id,
547 534 u'user_merged_pull_request:%d' % pull_request_id,
548 535 # The action below reflect that the post push actions were executed
549 536 u'user_commented_pull_request:%d' % pull_request_id,
550 537 ]
551 538 assert actions == expected_actions
552 539
553 540 user_logs = UserLog.query() \
554 541 .filter(UserLog.version == UserLog.VERSION_2) \
555 542 .order_by('-user_log_id').limit(1)
556 543 actions = [log.action for log in user_logs]
557 544 assert actions == ['user.push']
558 545 assert user_logs[0].action_data['commit_ids'] == pr_commit_ids
559 546
560 547 # Check post_push rcextension was really executed
561 548 push_calls = rhodecode.EXTENSIONS.calls['post_push']
562 549 assert len(push_calls) == 1
563 550 unused_last_call_args, last_call_kwargs = push_calls[0]
564 551 assert last_call_kwargs['action'] == 'push'
565 552 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
566 553
567 554 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
568 555 pull_request = pr_util.create_pull_request(mergeable=False)
569 556 pull_request_id = pull_request.pull_request_id
570 557 pull_request = PullRequest.get(pull_request_id)
571 558
572 559 response = self.app.post(
573 560 url(controller='pullrequests',
574 561 action='merge',
575 562 repo_name=pull_request.target_repo.scm_instance().name,
576 563 pull_request_id=str(pull_request.pull_request_id)),
577 564 params={'csrf_token': csrf_token}).follow()
578 565
579 566 assert response.status_int == 200
580 567 response.mustcontain(
581 568 'Merge is not currently possible because of below failed checks.')
582 569 response.mustcontain('Server-side pull request merging is disabled.')
583 570
584 571 @pytest.mark.skip_backends('svn')
585 572 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
586 573 pull_request = pr_util.create_pull_request(mergeable=True)
587 574 pull_request_id = pull_request.pull_request_id
588 575 repo_name = pull_request.target_repo.scm_instance().name,
589 576
590 577 response = self.app.post(
591 578 url(controller='pullrequests',
592 579 action='merge',
593 580 repo_name=str(repo_name[0]),
594 581 pull_request_id=str(pull_request_id)),
595 582 params={'csrf_token': csrf_token}).follow()
596 583
597 584 assert response.status_int == 200
598 585
599 586 response.mustcontain(
600 587 'Merge is not currently possible because of below failed checks.')
601 588 response.mustcontain('Pull request reviewer approval is pending.')
602 589
603 590 def test_update_source_revision(self, backend, csrf_token):
604 591 commits = [
605 592 {'message': 'ancestor'},
606 593 {'message': 'change'},
607 594 {'message': 'change-2'},
608 595 ]
609 596 commit_ids = backend.create_master_repo(commits)
610 597 target = backend.create_repo(heads=['ancestor'])
611 598 source = backend.create_repo(heads=['change'])
612 599
613 600 # create pr from a in source to A in target
614 601 pull_request = PullRequest()
615 602 pull_request.source_repo = source
616 603 # TODO: johbo: Make sure that we write the source ref this way!
617 604 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
618 605 branch=backend.default_branch_name, commit_id=commit_ids['change'])
619 606 pull_request.target_repo = target
620 607
621 608 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
622 609 branch=backend.default_branch_name,
623 610 commit_id=commit_ids['ancestor'])
624 611 pull_request.revisions = [commit_ids['change']]
625 612 pull_request.title = u"Test"
626 613 pull_request.description = u"Description"
627 614 pull_request.author = UserModel().get_by_username(
628 615 TEST_USER_ADMIN_LOGIN)
629 616 Session().add(pull_request)
630 617 Session().commit()
631 618 pull_request_id = pull_request.pull_request_id
632 619
633 620 # source has ancestor - change - change-2
634 621 backend.pull_heads(source, heads=['change-2'])
635 622
636 623 # update PR
637 624 self.app.post(
638 625 url(controller='pullrequests', action='update',
639 626 repo_name=target.repo_name,
640 627 pull_request_id=str(pull_request_id)),
641 628 params={'update_commits': 'true', '_method': 'put',
642 629 'csrf_token': csrf_token})
643 630
644 631 # check that we have now both revisions
645 632 pull_request = PullRequest.get(pull_request_id)
646 633 assert pull_request.revisions == [
647 634 commit_ids['change-2'], commit_ids['change']]
648 635
649 636 # TODO: johbo: this should be a test on its own
650 637 response = self.app.get(url(
651 638 controller='pullrequests', action='index',
652 639 repo_name=target.repo_name))
653 640 assert response.status_int == 200
654 641 assert 'Pull request updated to' in response.body
655 642 assert 'with 1 added, 0 removed commits.' in response.body
656 643
657 644 def test_update_target_revision(self, backend, csrf_token):
658 645 commits = [
659 646 {'message': 'ancestor'},
660 647 {'message': 'change'},
661 648 {'message': 'ancestor-new', 'parents': ['ancestor']},
662 649 {'message': 'change-rebased'},
663 650 ]
664 651 commit_ids = backend.create_master_repo(commits)
665 652 target = backend.create_repo(heads=['ancestor'])
666 653 source = backend.create_repo(heads=['change'])
667 654
668 655 # create pr from a in source to A in target
669 656 pull_request = PullRequest()
670 657 pull_request.source_repo = source
671 658 # TODO: johbo: Make sure that we write the source ref this way!
672 659 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
673 660 branch=backend.default_branch_name, commit_id=commit_ids['change'])
674 661 pull_request.target_repo = target
675 662 # TODO: johbo: Target ref should be branch based, since tip can jump
676 663 # from branch to branch
677 664 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
678 665 branch=backend.default_branch_name,
679 666 commit_id=commit_ids['ancestor'])
680 667 pull_request.revisions = [commit_ids['change']]
681 668 pull_request.title = u"Test"
682 669 pull_request.description = u"Description"
683 670 pull_request.author = UserModel().get_by_username(
684 671 TEST_USER_ADMIN_LOGIN)
685 672 Session().add(pull_request)
686 673 Session().commit()
687 674 pull_request_id = pull_request.pull_request_id
688 675
689 676 # target has ancestor - ancestor-new
690 677 # source has ancestor - ancestor-new - change-rebased
691 678 backend.pull_heads(target, heads=['ancestor-new'])
692 679 backend.pull_heads(source, heads=['change-rebased'])
693 680
694 681 # update PR
695 682 self.app.post(
696 683 url(controller='pullrequests', action='update',
697 684 repo_name=target.repo_name,
698 685 pull_request_id=str(pull_request_id)),
699 686 params={'update_commits': 'true', '_method': 'put',
700 687 'csrf_token': csrf_token},
701 688 status=200)
702 689
703 690 # check that we have now both revisions
704 691 pull_request = PullRequest.get(pull_request_id)
705 692 assert pull_request.revisions == [commit_ids['change-rebased']]
706 693 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
707 694 branch=backend.default_branch_name,
708 695 commit_id=commit_ids['ancestor-new'])
709 696
710 697 # TODO: johbo: This should be a test on its own
711 698 response = self.app.get(url(
712 699 controller='pullrequests', action='index',
713 700 repo_name=target.repo_name))
714 701 assert response.status_int == 200
715 702 assert 'Pull request updated to' in response.body
716 703 assert 'with 1 added, 1 removed commits.' in response.body
717 704
718 705 def test_update_of_ancestor_reference(self, backend, csrf_token):
719 706 commits = [
720 707 {'message': 'ancestor'},
721 708 {'message': 'change'},
722 709 {'message': 'change-2'},
723 710 {'message': 'ancestor-new', 'parents': ['ancestor']},
724 711 {'message': 'change-rebased'},
725 712 ]
726 713 commit_ids = backend.create_master_repo(commits)
727 714 target = backend.create_repo(heads=['ancestor'])
728 715 source = backend.create_repo(heads=['change'])
729 716
730 717 # create pr from a in source to A in target
731 718 pull_request = PullRequest()
732 719 pull_request.source_repo = source
733 720 # TODO: johbo: Make sure that we write the source ref this way!
734 721 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
735 722 branch=backend.default_branch_name,
736 723 commit_id=commit_ids['change'])
737 724 pull_request.target_repo = target
738 725 # TODO: johbo: Target ref should be branch based, since tip can jump
739 726 # from branch to branch
740 727 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
741 728 branch=backend.default_branch_name,
742 729 commit_id=commit_ids['ancestor'])
743 730 pull_request.revisions = [commit_ids['change']]
744 731 pull_request.title = u"Test"
745 732 pull_request.description = u"Description"
746 733 pull_request.author = UserModel().get_by_username(
747 734 TEST_USER_ADMIN_LOGIN)
748 735 Session().add(pull_request)
749 736 Session().commit()
750 737 pull_request_id = pull_request.pull_request_id
751 738
752 739 # target has ancestor - ancestor-new
753 740 # source has ancestor - ancestor-new - change-rebased
754 741 backend.pull_heads(target, heads=['ancestor-new'])
755 742 backend.pull_heads(source, heads=['change-rebased'])
756 743
757 744 # update PR
758 745 self.app.post(
759 746 url(controller='pullrequests', action='update',
760 747 repo_name=target.repo_name,
761 748 pull_request_id=str(pull_request_id)),
762 749 params={'update_commits': 'true', '_method': 'put',
763 750 'csrf_token': csrf_token},
764 751 status=200)
765 752
766 753 # Expect the target reference to be updated correctly
767 754 pull_request = PullRequest.get(pull_request_id)
768 755 assert pull_request.revisions == [commit_ids['change-rebased']]
769 756 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
770 757 branch=backend.default_branch_name,
771 758 commit_id=commit_ids['ancestor-new'])
772 759 assert pull_request.target_ref == expected_target_ref
773 760
774 761 def test_remove_pull_request_branch(self, backend_git, csrf_token):
775 762 branch_name = 'development'
776 763 commits = [
777 764 {'message': 'initial-commit'},
778 765 {'message': 'old-feature'},
779 766 {'message': 'new-feature', 'branch': branch_name},
780 767 ]
781 768 repo = backend_git.create_repo(commits)
782 769 commit_ids = backend_git.commit_ids
783 770
784 771 pull_request = PullRequest()
785 772 pull_request.source_repo = repo
786 773 pull_request.target_repo = repo
787 774 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
788 775 branch=branch_name, commit_id=commit_ids['new-feature'])
789 776 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
790 777 branch=backend_git.default_branch_name,
791 778 commit_id=commit_ids['old-feature'])
792 779 pull_request.revisions = [commit_ids['new-feature']]
793 780 pull_request.title = u"Test"
794 781 pull_request.description = u"Description"
795 782 pull_request.author = UserModel().get_by_username(
796 783 TEST_USER_ADMIN_LOGIN)
797 784 Session().add(pull_request)
798 785 Session().commit()
799 786
800 787 vcs = repo.scm_instance()
801 788 vcs.remove_ref('refs/heads/{}'.format(branch_name))
802 789
803 790 response = self.app.get(url(
804 791 controller='pullrequests', action='show',
805 792 repo_name=repo.repo_name,
806 793 pull_request_id=str(pull_request.pull_request_id)))
807 794
808 795 assert response.status_int == 200
809 796 assert_response = AssertResponse(response)
810 797 assert_response.element_contains(
811 798 '#changeset_compare_view_content .alert strong',
812 799 'Missing commits')
813 800 assert_response.element_contains(
814 801 '#changeset_compare_view_content .alert',
815 802 'This pull request cannot be displayed, because one or more'
816 803 ' commits no longer exist in the source repository.')
817 804
818 805 def test_strip_commits_from_pull_request(
819 806 self, backend, pr_util, csrf_token):
820 807 commits = [
821 808 {'message': 'initial-commit'},
822 809 {'message': 'old-feature'},
823 810 {'message': 'new-feature', 'parents': ['initial-commit']},
824 811 ]
825 812 pull_request = pr_util.create_pull_request(
826 813 commits, target_head='initial-commit', source_head='new-feature',
827 814 revisions=['new-feature'])
828 815
829 816 vcs = pr_util.source_repository.scm_instance()
830 817 if backend.alias == 'git':
831 818 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
832 819 else:
833 820 vcs.strip(pr_util.commit_ids['new-feature'])
834 821
835 822 response = self.app.get(url(
836 823 controller='pullrequests', action='show',
837 824 repo_name=pr_util.target_repository.repo_name,
838 825 pull_request_id=str(pull_request.pull_request_id)))
839 826
840 827 assert response.status_int == 200
841 828 assert_response = AssertResponse(response)
842 829 assert_response.element_contains(
843 830 '#changeset_compare_view_content .alert strong',
844 831 'Missing commits')
845 832 assert_response.element_contains(
846 833 '#changeset_compare_view_content .alert',
847 834 'This pull request cannot be displayed, because one or more'
848 835 ' commits no longer exist in the source repository.')
849 836 assert_response.element_contains(
850 837 '#update_commits',
851 838 'Update commits')
852 839
853 840 def test_strip_commits_and_update(
854 841 self, backend, pr_util, csrf_token):
855 842 commits = [
856 843 {'message': 'initial-commit'},
857 844 {'message': 'old-feature'},
858 845 {'message': 'new-feature', 'parents': ['old-feature']},
859 846 ]
860 847 pull_request = pr_util.create_pull_request(
861 848 commits, target_head='old-feature', source_head='new-feature',
862 849 revisions=['new-feature'], mergeable=True)
863 850
864 851 vcs = pr_util.source_repository.scm_instance()
865 852 if backend.alias == 'git':
866 853 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
867 854 else:
868 855 vcs.strip(pr_util.commit_ids['new-feature'])
869 856
870 857 response = self.app.post(
871 858 url(controller='pullrequests', action='update',
872 859 repo_name=pull_request.target_repo.repo_name,
873 860 pull_request_id=str(pull_request.pull_request_id)),
874 861 params={'update_commits': 'true', '_method': 'put',
875 862 'csrf_token': csrf_token})
876 863
877 864 assert response.status_int == 200
878 865 assert response.body == 'true'
879 866
880 867 # Make sure that after update, it won't raise 500 errors
881 868 response = self.app.get(url(
882 869 controller='pullrequests', action='show',
883 870 repo_name=pr_util.target_repository.repo_name,
884 871 pull_request_id=str(pull_request.pull_request_id)))
885 872
886 873 assert response.status_int == 200
887 874 assert_response = AssertResponse(response)
888 875 assert_response.element_contains(
889 876 '#changeset_compare_view_content .alert strong',
890 877 'Missing commits')
891 878
892 879 def test_branch_is_a_link(self, pr_util):
893 880 pull_request = pr_util.create_pull_request()
894 881 pull_request.source_ref = 'branch:origin:1234567890abcdef'
895 882 pull_request.target_ref = 'branch:target:abcdef1234567890'
896 883 Session().add(pull_request)
897 884 Session().commit()
898 885
899 886 response = self.app.get(url(
900 887 controller='pullrequests', action='show',
901 888 repo_name=pull_request.target_repo.scm_instance().name,
902 889 pull_request_id=str(pull_request.pull_request_id)))
903 890 assert response.status_int == 200
904 891 assert_response = AssertResponse(response)
905 892
906 893 origin = assert_response.get_element('.pr-origininfo .tag')
907 894 origin_children = origin.getchildren()
908 895 assert len(origin_children) == 1
909 896 target = assert_response.get_element('.pr-targetinfo .tag')
910 897 target_children = target.getchildren()
911 898 assert len(target_children) == 1
912 899
913 900 expected_origin_link = url(
914 901 'changelog_home',
915 902 repo_name=pull_request.source_repo.scm_instance().name,
916 903 branch='origin')
917 904 expected_target_link = url(
918 905 'changelog_home',
919 906 repo_name=pull_request.target_repo.scm_instance().name,
920 907 branch='target')
921 908 assert origin_children[0].attrib['href'] == expected_origin_link
922 909 assert origin_children[0].text == 'branch: origin'
923 910 assert target_children[0].attrib['href'] == expected_target_link
924 911 assert target_children[0].text == 'branch: target'
925 912
926 913 def test_bookmark_is_not_a_link(self, pr_util):
927 914 pull_request = pr_util.create_pull_request()
928 915 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
929 916 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
930 917 Session().add(pull_request)
931 918 Session().commit()
932 919
933 920 response = self.app.get(url(
934 921 controller='pullrequests', action='show',
935 922 repo_name=pull_request.target_repo.scm_instance().name,
936 923 pull_request_id=str(pull_request.pull_request_id)))
937 924 assert response.status_int == 200
938 925 assert_response = AssertResponse(response)
939 926
940 927 origin = assert_response.get_element('.pr-origininfo .tag')
941 928 assert origin.text.strip() == 'bookmark: origin'
942 929 assert origin.getchildren() == []
943 930
944 931 target = assert_response.get_element('.pr-targetinfo .tag')
945 932 assert target.text.strip() == 'bookmark: target'
946 933 assert target.getchildren() == []
947 934
948 935 def test_tag_is_not_a_link(self, pr_util):
949 936 pull_request = pr_util.create_pull_request()
950 937 pull_request.source_ref = 'tag:origin:1234567890abcdef'
951 938 pull_request.target_ref = 'tag:target:abcdef1234567890'
952 939 Session().add(pull_request)
953 940 Session().commit()
954 941
955 942 response = self.app.get(url(
956 943 controller='pullrequests', action='show',
957 944 repo_name=pull_request.target_repo.scm_instance().name,
958 945 pull_request_id=str(pull_request.pull_request_id)))
959 946 assert response.status_int == 200
960 947 assert_response = AssertResponse(response)
961 948
962 949 origin = assert_response.get_element('.pr-origininfo .tag')
963 950 assert origin.text.strip() == 'tag: origin'
964 951 assert origin.getchildren() == []
965 952
966 953 target = assert_response.get_element('.pr-targetinfo .tag')
967 954 assert target.text.strip() == 'tag: target'
968 955 assert target.getchildren() == []
969 956
970 957 def test_description_is_escaped_on_index_page(self, backend, pr_util):
971 958 xss_description = "<script>alert('Hi!')</script>"
972 959 pull_request = pr_util.create_pull_request(description=xss_description)
973 960 response = self.app.get(url(
974 961 controller='pullrequests', action='show_all',
975 962 repo_name=pull_request.target_repo.repo_name))
976 963 response.mustcontain(
977 964 "&lt;script&gt;alert(&#39;Hi!&#39;)&lt;/script&gt;")
978 965
979 966 @pytest.mark.parametrize('mergeable', [True, False])
980 967 def test_shadow_repository_link(
981 968 self, mergeable, pr_util, http_host_stub):
982 969 """
983 970 Check that the pull request summary page displays a link to the shadow
984 971 repository if the pull request is mergeable. If it is not mergeable
985 972 the link should not be displayed.
986 973 """
987 974 pull_request = pr_util.create_pull_request(
988 975 mergeable=mergeable, enable_notifications=False)
989 976 target_repo = pull_request.target_repo.scm_instance()
990 977 pr_id = pull_request.pull_request_id
991 978 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
992 979 host=http_host_stub, repo=target_repo.name, pr_id=pr_id)
993 980
994 981 response = self.app.get(url(
995 982 controller='pullrequests', action='show',
996 983 repo_name=target_repo.name,
997 984 pull_request_id=str(pr_id)))
998 985
999 986 assertr = AssertResponse(response)
1000 987 if mergeable:
1001 988 assertr.element_value_contains(
1002 989 'div.pr-mergeinfo input', shadow_url)
1003 990 assertr.element_value_contains(
1004 991 'div.pr-mergeinfo input', 'pr-merge')
1005 992 else:
1006 993 assertr.no_element_exists('div.pr-mergeinfo')
1007 994
1008 995
1009 996 @pytest.mark.usefixtures('app')
1010 997 @pytest.mark.backends("git", "hg")
1011 998 class TestPullrequestsControllerDelete(object):
1012 999 def test_pull_request_delete_button_permissions_admin(
1013 1000 self, autologin_user, user_admin, pr_util):
1014 1001 pull_request = pr_util.create_pull_request(
1015 1002 author=user_admin.username, enable_notifications=False)
1016 1003
1017 1004 response = self.app.get(url(
1018 1005 controller='pullrequests', action='show',
1019 1006 repo_name=pull_request.target_repo.scm_instance().name,
1020 1007 pull_request_id=str(pull_request.pull_request_id)))
1021 1008
1022 1009 response.mustcontain('id="delete_pullrequest"')
1023 1010 response.mustcontain('Confirm to delete this pull request')
1024 1011
1025 1012 def test_pull_request_delete_button_permissions_owner(
1026 1013 self, autologin_regular_user, user_regular, pr_util):
1027 1014 pull_request = pr_util.create_pull_request(
1028 1015 author=user_regular.username, enable_notifications=False)
1029 1016
1030 1017 response = self.app.get(url(
1031 1018 controller='pullrequests', action='show',
1032 1019 repo_name=pull_request.target_repo.scm_instance().name,
1033 1020 pull_request_id=str(pull_request.pull_request_id)))
1034 1021
1035 1022 response.mustcontain('id="delete_pullrequest"')
1036 1023 response.mustcontain('Confirm to delete this pull request')
1037 1024
1038 1025 def test_pull_request_delete_button_permissions_forbidden(
1039 1026 self, autologin_regular_user, user_regular, user_admin, pr_util):
1040 1027 pull_request = pr_util.create_pull_request(
1041 1028 author=user_admin.username, enable_notifications=False)
1042 1029
1043 1030 response = self.app.get(url(
1044 1031 controller='pullrequests', action='show',
1045 1032 repo_name=pull_request.target_repo.scm_instance().name,
1046 1033 pull_request_id=str(pull_request.pull_request_id)))
1047 1034 response.mustcontain(no=['id="delete_pullrequest"'])
1048 1035 response.mustcontain(no=['Confirm to delete this pull request'])
1049 1036
1050 1037 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1051 1038 self, autologin_regular_user, user_regular, user_admin, pr_util,
1052 1039 user_util):
1053 1040
1054 1041 pull_request = pr_util.create_pull_request(
1055 1042 author=user_admin.username, enable_notifications=False)
1056 1043
1057 1044 user_util.grant_user_permission_to_repo(
1058 1045 pull_request.target_repo, user_regular,
1059 1046 'repository.write')
1060 1047
1061 1048 response = self.app.get(url(
1062 1049 controller='pullrequests', action='show',
1063 1050 repo_name=pull_request.target_repo.scm_instance().name,
1064 1051 pull_request_id=str(pull_request.pull_request_id)))
1065 1052
1066 1053 response.mustcontain('id="open_edit_pullrequest"')
1067 1054 response.mustcontain('id="delete_pullrequest"')
1068 1055 response.mustcontain(no=['Confirm to delete this pull request'])
1069 1056
1070 1057
1071 1058 def assert_pull_request_status(pull_request, expected_status):
1072 1059 status = ChangesetStatusModel().calculated_review_status(
1073 1060 pull_request=pull_request)
1074 1061 assert status == expected_status
1075 1062
1076 1063
1077 1064 @pytest.mark.parametrize('action', ['show_all', 'index', 'create'])
1078 1065 @pytest.mark.usefixtures("autologin_user")
1079 1066 def test_redirects_to_repo_summary_for_svn_repositories(
1080 1067 backend_svn, app, action):
1081 1068 denied_actions = ['show_all', 'index', 'create']
1082 1069 for action in denied_actions:
1083 1070 response = app.get(url(
1084 1071 controller='pullrequests', action=action,
1085 1072 repo_name=backend_svn.repo_name))
1086 1073 assert response.status_int == 302
1087 1074
1088 1075 # Not allowed, redirect to the summary
1089 1076 redirected = response.follow()
1090 1077 summary_url = url('summary_home', repo_name=backend_svn.repo_name)
1091 1078
1092 1079 # URL adds leading slash and path doesn't have it
1093 1080 assert redirected.req.path == summary_url
1094 1081
1095 1082
1096 1083 def test_delete_comment_returns_404_if_comment_does_not_exist(pylonsapp):
1097 1084 # TODO: johbo: Global import not possible because models.forms blows up
1098 1085 from rhodecode.controllers.pullrequests import PullrequestsController
1099 1086 controller = PullrequestsController()
1100 1087 patcher = mock.patch(
1101 1088 'rhodecode.model.db.BaseModel.get', return_value=None)
1102 1089 with pytest.raises(HTTPNotFound), patcher:
1103 1090 controller._delete_comment(1)
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now