##// END OF EJS Templates
core: revamp of automation/scheduler/artifacts EE functionality
super-admin -
r5137:f3cd5ebe default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,38 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20
21 from rhodecode.apps._base import BaseAppView
22 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
23
24 log = logging.getLogger(__name__)
25
26
27 class AdminAutomationView(BaseAppView):
28
29 def load_default_context(self):
30 c = self._get_local_tmpl_context()
31 return c
32
33 @LoginRequired()
34 @HasPermissionAllDecorator('hg.admin')
35 def automation(self):
36 c = self.load_default_context()
37 c.active = 'automation'
38 return self._get_template_context(c)
@@ -0,0 +1,38 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20
21 from rhodecode.apps._base import BaseAppView
22 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
23
24 log = logging.getLogger(__name__)
25
26
27 class AdminSchedulerView(BaseAppView):
28
29 def load_default_context(self):
30 c = self._get_local_tmpl_context()
31 return c
32
33 @LoginRequired()
34 @HasPermissionAllDecorator('hg.admin')
35 def scheduler(self):
36 c = self.load_default_context()
37 c.active = 'scheduler'
38 return self._get_template_context(c)
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,858 +1,858 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import time
19 import time
20 import logging
20 import logging
21 import operator
21 import operator
22
22
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
24
24
25 from rhodecode.lib import helpers as h, diffs, rc_cache
25 from rhodecode.lib import helpers as h, diffs, rc_cache
26 from rhodecode.lib.str_utils import safe_str
26 from rhodecode.lib.str_utils import safe_str
27 from rhodecode.lib.utils import repo_name_slug
27 from rhodecode.lib.utils import repo_name_slug
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 StrictAttributeDict, str2bool, safe_int, datetime_to_time)
29 StrictAttributeDict, str2bool, safe_int, datetime_to_time)
30 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
30 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
32 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
33 from rhodecode.model import repo
33 from rhodecode.model import repo
34 from rhodecode.model import repo_group
34 from rhodecode.model import repo_group
35 from rhodecode.model import user_group
35 from rhodecode.model import user_group
36 from rhodecode.model import user
36 from rhodecode.model import user
37 from rhodecode.model.db import User
37 from rhodecode.model.db import User
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
39 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
40 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 ADMIN_PREFIX = '/_admin'
45 ADMIN_PREFIX: str = '/_admin'
46 STATIC_FILE_PREFIX = '/_static'
46 STATIC_FILE_PREFIX: str = '/_static'
47
47
48 URL_NAME_REQUIREMENTS = {
48 URL_NAME_REQUIREMENTS = {
49 # group name can have a slash in them, but they must not end with a slash
49 # group name can have a slash in them, but they must not end with a slash
50 'group_name': r'.*?[^/]',
50 'group_name': r'.*?[^/]',
51 'repo_group_name': r'.*?[^/]',
51 'repo_group_name': r'.*?[^/]',
52 # repo names can have a slash in them, but they must not end with a slash
52 # repo names can have a slash in them, but they must not end with a slash
53 'repo_name': r'.*?[^/]',
53 'repo_name': r'.*?[^/]',
54 # file path eats up everything at the end
54 # file path eats up everything at the end
55 'f_path': r'.*',
55 'f_path': r'.*',
56 # reference types
56 # reference types
57 'source_ref_type': r'(branch|book|tag|rev|\%\(source_ref_type\)s)',
57 'source_ref_type': r'(branch|book|tag|rev|\%\(source_ref_type\)s)',
58 'target_ref_type': r'(branch|book|tag|rev|\%\(target_ref_type\)s)',
58 'target_ref_type': r'(branch|book|tag|rev|\%\(target_ref_type\)s)',
59 }
59 }
60
60
61
61
62 def add_route_with_slash(config,name, pattern, **kw):
62 def add_route_with_slash(config,name, pattern, **kw):
63 config.add_route(name, pattern, **kw)
63 config.add_route(name, pattern, **kw)
64 if not pattern.endswith('/'):
64 if not pattern.endswith('/'):
65 config.add_route(name + '_slash', pattern + '/', **kw)
65 config.add_route(name + '_slash', pattern + '/', **kw)
66
66
67
67
68 def add_route_requirements(route_path, requirements=None):
68 def add_route_requirements(route_path, requirements=None):
69 """
69 """
70 Adds regex requirements to pyramid routes using a mapping dict
70 Adds regex requirements to pyramid routes using a mapping dict
71 e.g::
71 e.g::
72 add_route_requirements('{repo_name}/settings')
72 add_route_requirements('{repo_name}/settings')
73 """
73 """
74 requirements = requirements or URL_NAME_REQUIREMENTS
74 requirements = requirements or URL_NAME_REQUIREMENTS
75 for key, regex in list(requirements.items()):
75 for key, regex in list(requirements.items()):
76 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
76 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
77 return route_path
77 return route_path
78
78
79
79
80 def get_format_ref_id(repo):
80 def get_format_ref_id(repo):
81 """Returns a `repo` specific reference formatter function"""
81 """Returns a `repo` specific reference formatter function"""
82 if h.is_svn(repo):
82 if h.is_svn(repo):
83 return _format_ref_id_svn
83 return _format_ref_id_svn
84 else:
84 else:
85 return _format_ref_id
85 return _format_ref_id
86
86
87
87
88 def _format_ref_id(name, raw_id):
88 def _format_ref_id(name, raw_id):
89 """Default formatting of a given reference `name`"""
89 """Default formatting of a given reference `name`"""
90 return name
90 return name
91
91
92
92
93 def _format_ref_id_svn(name, raw_id):
93 def _format_ref_id_svn(name, raw_id):
94 """Special way of formatting a reference for Subversion including path"""
94 """Special way of formatting a reference for Subversion including path"""
95 return f'{name}@{raw_id}'
95 return f'{name}@{raw_id}'
96
96
97
97
98 class TemplateArgs(StrictAttributeDict):
98 class TemplateArgs(StrictAttributeDict):
99 pass
99 pass
100
100
101
101
102 class BaseAppView(object):
102 class BaseAppView(object):
103
103
104 def __init__(self, context, request):
104 def __init__(self, context, request):
105 self.request = request
105 self.request = request
106 self.context = context
106 self.context = context
107 self.session = request.session
107 self.session = request.session
108 if not hasattr(request, 'user'):
108 if not hasattr(request, 'user'):
109 # NOTE(marcink): edge case, we ended up in matched route
109 # NOTE(marcink): edge case, we ended up in matched route
110 # but probably of web-app context, e.g API CALL/VCS CALL
110 # but probably of web-app context, e.g API CALL/VCS CALL
111 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
111 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
112 log.warning('Unable to process request `%s` in this scope', request)
112 log.warning('Unable to process request `%s` in this scope', request)
113 raise HTTPBadRequest()
113 raise HTTPBadRequest()
114
114
115 self._rhodecode_user = request.user # auth user
115 self._rhodecode_user = request.user # auth user
116 self._rhodecode_db_user = self._rhodecode_user.get_instance()
116 self._rhodecode_db_user = self._rhodecode_user.get_instance()
117 self._maybe_needs_password_change(
117 self._maybe_needs_password_change(
118 request.matched_route.name, self._rhodecode_db_user)
118 request.matched_route.name, self._rhodecode_db_user)
119
119
120 def _maybe_needs_password_change(self, view_name, user_obj):
120 def _maybe_needs_password_change(self, view_name, user_obj):
121
121
122 dont_check_views = [
122 dont_check_views = [
123 'channelstream_connect',
123 'channelstream_connect',
124 'ops_ping'
124 'ops_ping'
125 ]
125 ]
126 if view_name in dont_check_views:
126 if view_name in dont_check_views:
127 return
127 return
128
128
129 log.debug('Checking if user %s needs password change on view %s',
129 log.debug('Checking if user %s needs password change on view %s',
130 user_obj, view_name)
130 user_obj, view_name)
131
131
132 skip_user_views = [
132 skip_user_views = [
133 'logout', 'login',
133 'logout', 'login',
134 'my_account_password', 'my_account_password_update'
134 'my_account_password', 'my_account_password_update'
135 ]
135 ]
136
136
137 if not user_obj:
137 if not user_obj:
138 return
138 return
139
139
140 if user_obj.username == User.DEFAULT_USER:
140 if user_obj.username == User.DEFAULT_USER:
141 return
141 return
142
142
143 now = time.time()
143 now = time.time()
144 should_change = user_obj.user_data.get('force_password_change')
144 should_change = user_obj.user_data.get('force_password_change')
145 change_after = safe_int(should_change) or 0
145 change_after = safe_int(should_change) or 0
146 if should_change and now > change_after:
146 if should_change and now > change_after:
147 log.debug('User %s requires password change', user_obj)
147 log.debug('User %s requires password change', user_obj)
148 h.flash('You are required to change your password', 'warning',
148 h.flash('You are required to change your password', 'warning',
149 ignore_duplicate=True)
149 ignore_duplicate=True)
150
150
151 if view_name not in skip_user_views:
151 if view_name not in skip_user_views:
152 raise HTTPFound(
152 raise HTTPFound(
153 self.request.route_path('my_account_password'))
153 self.request.route_path('my_account_password'))
154
154
155 def _log_creation_exception(self, e, repo_name):
155 def _log_creation_exception(self, e, repo_name):
156 _ = self.request.translate
156 _ = self.request.translate
157 reason = None
157 reason = None
158 if len(e.args) == 2:
158 if len(e.args) == 2:
159 reason = e.args[1]
159 reason = e.args[1]
160
160
161 if reason == 'INVALID_CERTIFICATE':
161 if reason == 'INVALID_CERTIFICATE':
162 log.exception(
162 log.exception(
163 'Exception creating a repository: invalid certificate')
163 'Exception creating a repository: invalid certificate')
164 msg = (_('Error creating repository %s: invalid certificate')
164 msg = (_('Error creating repository %s: invalid certificate')
165 % repo_name)
165 % repo_name)
166 else:
166 else:
167 log.exception("Exception creating a repository")
167 log.exception("Exception creating a repository")
168 msg = (_('Error creating repository %s')
168 msg = (_('Error creating repository %s')
169 % repo_name)
169 % repo_name)
170 return msg
170 return msg
171
171
172 def _get_local_tmpl_context(self, include_app_defaults=True):
172 def _get_local_tmpl_context(self, include_app_defaults=True):
173 c = TemplateArgs()
173 c = TemplateArgs()
174 c.auth_user = self.request.user
174 c.auth_user = self.request.user
175 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
175 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
176 c.rhodecode_user = self.request.user
176 c.rhodecode_user = self.request.user
177
177
178 if include_app_defaults:
178 if include_app_defaults:
179 from rhodecode.lib.base import attach_context_attributes
179 from rhodecode.lib.base import attach_context_attributes
180 attach_context_attributes(c, self.request, self.request.user.user_id)
180 attach_context_attributes(c, self.request, self.request.user.user_id)
181
181
182 c.is_super_admin = c.auth_user.is_admin
182 c.is_super_admin = c.auth_user.is_admin
183
183
184 c.can_create_repo = c.is_super_admin
184 c.can_create_repo = c.is_super_admin
185 c.can_create_repo_group = c.is_super_admin
185 c.can_create_repo_group = c.is_super_admin
186 c.can_create_user_group = c.is_super_admin
186 c.can_create_user_group = c.is_super_admin
187
187
188 c.is_delegated_admin = False
188 c.is_delegated_admin = False
189
189
190 if not c.auth_user.is_default and not c.is_super_admin:
190 if not c.auth_user.is_default and not c.is_super_admin:
191 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
191 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
192 user=self.request.user)
192 user=self.request.user)
193 repositories = c.auth_user.repositories_admin or c.can_create_repo
193 repositories = c.auth_user.repositories_admin or c.can_create_repo
194
194
195 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
195 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
196 user=self.request.user)
196 user=self.request.user)
197 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
197 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
198
198
199 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
199 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
200 user=self.request.user)
200 user=self.request.user)
201 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
201 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
202 # delegated admin can create, or manage some objects
202 # delegated admin can create, or manage some objects
203 c.is_delegated_admin = repositories or repository_groups or user_groups
203 c.is_delegated_admin = repositories or repository_groups or user_groups
204 return c
204 return c
205
205
206 def _get_template_context(self, tmpl_args, **kwargs):
206 def _get_template_context(self, tmpl_args, **kwargs):
207
207
208 local_tmpl_args = {
208 local_tmpl_args = {
209 'defaults': {},
209 'defaults': {},
210 'errors': {},
210 'errors': {},
211 'c': tmpl_args
211 'c': tmpl_args
212 }
212 }
213 local_tmpl_args.update(kwargs)
213 local_tmpl_args.update(kwargs)
214 return local_tmpl_args
214 return local_tmpl_args
215
215
216 def load_default_context(self):
216 def load_default_context(self):
217 """
217 """
218 example:
218 example:
219
219
220 def load_default_context(self):
220 def load_default_context(self):
221 c = self._get_local_tmpl_context()
221 c = self._get_local_tmpl_context()
222 c.custom_var = 'foobar'
222 c.custom_var = 'foobar'
223
223
224 return c
224 return c
225 """
225 """
226 raise NotImplementedError('Needs implementation in view class')
226 raise NotImplementedError('Needs implementation in view class')
227
227
228
228
229 class RepoAppView(BaseAppView):
229 class RepoAppView(BaseAppView):
230
230
231 def __init__(self, context, request):
231 def __init__(self, context, request):
232 super().__init__(context, request)
232 super().__init__(context, request)
233 self.db_repo = request.db_repo
233 self.db_repo = request.db_repo
234 self.db_repo_name = self.db_repo.repo_name
234 self.db_repo_name = self.db_repo.repo_name
235 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
235 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
236 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
236 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
237 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
237 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
238
238
239 def _handle_missing_requirements(self, error):
239 def _handle_missing_requirements(self, error):
240 log.error(
240 log.error(
241 'Requirements are missing for repository %s: %s',
241 'Requirements are missing for repository %s: %s',
242 self.db_repo_name, safe_str(error))
242 self.db_repo_name, safe_str(error))
243
243
244 def _prepare_and_set_clone_url(self, c):
244 def _prepare_and_set_clone_url(self, c):
245 username = ''
245 username = ''
246 if self._rhodecode_user.username != User.DEFAULT_USER:
246 if self._rhodecode_user.username != User.DEFAULT_USER:
247 username = self._rhodecode_user.username
247 username = self._rhodecode_user.username
248
248
249 _def_clone_uri = c.clone_uri_tmpl
249 _def_clone_uri = c.clone_uri_tmpl
250 _def_clone_uri_id = c.clone_uri_id_tmpl
250 _def_clone_uri_id = c.clone_uri_id_tmpl
251 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
251 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
252
252
253 c.clone_repo_url = self.db_repo.clone_url(
253 c.clone_repo_url = self.db_repo.clone_url(
254 user=username, uri_tmpl=_def_clone_uri)
254 user=username, uri_tmpl=_def_clone_uri)
255 c.clone_repo_url_id = self.db_repo.clone_url(
255 c.clone_repo_url_id = self.db_repo.clone_url(
256 user=username, uri_tmpl=_def_clone_uri_id)
256 user=username, uri_tmpl=_def_clone_uri_id)
257 c.clone_repo_url_ssh = self.db_repo.clone_url(
257 c.clone_repo_url_ssh = self.db_repo.clone_url(
258 uri_tmpl=_def_clone_uri_ssh, ssh=True)
258 uri_tmpl=_def_clone_uri_ssh, ssh=True)
259
259
260 def _get_local_tmpl_context(self, include_app_defaults=True):
260 def _get_local_tmpl_context(self, include_app_defaults=True):
261 _ = self.request.translate
261 _ = self.request.translate
262 c = super()._get_local_tmpl_context(
262 c = super()._get_local_tmpl_context(
263 include_app_defaults=include_app_defaults)
263 include_app_defaults=include_app_defaults)
264
264
265 # register common vars for this type of view
265 # register common vars for this type of view
266 c.rhodecode_db_repo = self.db_repo
266 c.rhodecode_db_repo = self.db_repo
267 c.repo_name = self.db_repo_name
267 c.repo_name = self.db_repo_name
268 c.repository_pull_requests = self.db_repo_pull_requests
268 c.repository_pull_requests = self.db_repo_pull_requests
269 c.repository_artifacts = self.db_repo_artifacts
269 c.repository_artifacts = self.db_repo_artifacts
270 c.repository_is_user_following = ScmModel().is_following_repo(
270 c.repository_is_user_following = ScmModel().is_following_repo(
271 self.db_repo_name, self._rhodecode_user.user_id)
271 self.db_repo_name, self._rhodecode_user.user_id)
272 self.path_filter = PathFilter(None)
272 self.path_filter = PathFilter(None)
273
273
274 c.repository_requirements_missing = {}
274 c.repository_requirements_missing = {}
275 try:
275 try:
276 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
276 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
277 # NOTE(marcink):
277 # NOTE(marcink):
278 # comparison to None since if it's an object __bool__ is expensive to
278 # comparison to None since if it's an object __bool__ is expensive to
279 # calculate
279 # calculate
280 if self.rhodecode_vcs_repo is not None:
280 if self.rhodecode_vcs_repo is not None:
281 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
281 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
282 c.auth_user.username)
282 c.auth_user.username)
283 self.path_filter = PathFilter(path_perms)
283 self.path_filter = PathFilter(path_perms)
284 except RepositoryRequirementError as e:
284 except RepositoryRequirementError as e:
285 c.repository_requirements_missing = {'error': str(e)}
285 c.repository_requirements_missing = {'error': str(e)}
286 self._handle_missing_requirements(e)
286 self._handle_missing_requirements(e)
287 self.rhodecode_vcs_repo = None
287 self.rhodecode_vcs_repo = None
288
288
289 c.path_filter = self.path_filter # used by atom_feed_entry.mako
289 c.path_filter = self.path_filter # used by atom_feed_entry.mako
290
290
291 if self.rhodecode_vcs_repo is None:
291 if self.rhodecode_vcs_repo is None:
292 # unable to fetch this repo as vcs instance, report back to user
292 # unable to fetch this repo as vcs instance, report back to user
293 log.debug('Repository was not found on filesystem, check if it exists or is not damaged')
293 log.debug('Repository was not found on filesystem, check if it exists or is not damaged')
294 h.flash(_(
294 h.flash(_(
295 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
295 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
296 "Please check if it exist, or is not damaged.") %
296 "Please check if it exist, or is not damaged.") %
297 {'repo_name': c.repo_name},
297 {'repo_name': c.repo_name},
298 category='error', ignore_duplicate=True)
298 category='error', ignore_duplicate=True)
299 if c.repository_requirements_missing:
299 if c.repository_requirements_missing:
300 route = self.request.matched_route.name
300 route = self.request.matched_route.name
301 if route.startswith(('edit_repo', 'repo_summary')):
301 if route.startswith(('edit_repo', 'repo_summary')):
302 # allow summary and edit repo on missing requirements
302 # allow summary and edit repo on missing requirements
303 return c
303 return c
304
304
305 raise HTTPFound(
305 raise HTTPFound(
306 h.route_path('repo_summary', repo_name=self.db_repo_name))
306 h.route_path('repo_summary', repo_name=self.db_repo_name))
307
307
308 else: # redirect if we don't show missing requirements
308 else: # redirect if we don't show missing requirements
309 raise HTTPFound(h.route_path('home'))
309 raise HTTPFound(h.route_path('home'))
310
310
311 c.has_origin_repo_read_perm = False
311 c.has_origin_repo_read_perm = False
312 if self.db_repo.fork:
312 if self.db_repo.fork:
313 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
313 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
314 'repository.write', 'repository.read', 'repository.admin')(
314 'repository.write', 'repository.read', 'repository.admin')(
315 self.db_repo.fork.repo_name, 'summary fork link')
315 self.db_repo.fork.repo_name, 'summary fork link')
316
316
317 return c
317 return c
318
318
319 def _get_f_path_unchecked(self, matchdict, default=None):
319 def _get_f_path_unchecked(self, matchdict, default=None):
320 """
320 """
321 Should only be used by redirects, everything else should call _get_f_path
321 Should only be used by redirects, everything else should call _get_f_path
322 """
322 """
323 f_path = matchdict.get('f_path')
323 f_path = matchdict.get('f_path')
324 if f_path:
324 if f_path:
325 # fix for multiple initial slashes that causes errors for GIT
325 # fix for multiple initial slashes that causes errors for GIT
326 return f_path.lstrip('/')
326 return f_path.lstrip('/')
327
327
328 return default
328 return default
329
329
330 def _get_f_path(self, matchdict, default=None):
330 def _get_f_path(self, matchdict, default=None):
331 f_path_match = self._get_f_path_unchecked(matchdict, default)
331 f_path_match = self._get_f_path_unchecked(matchdict, default)
332 return self.path_filter.assert_path_permissions(f_path_match)
332 return self.path_filter.assert_path_permissions(f_path_match)
333
333
334 def _get_general_setting(self, target_repo, settings_key, default=False):
334 def _get_general_setting(self, target_repo, settings_key, default=False):
335 settings_model = VcsSettingsModel(repo=target_repo)
335 settings_model = VcsSettingsModel(repo=target_repo)
336 settings = settings_model.get_general_settings()
336 settings = settings_model.get_general_settings()
337 return settings.get(settings_key, default)
337 return settings.get(settings_key, default)
338
338
339 def _get_repo_setting(self, target_repo, settings_key, default=False):
339 def _get_repo_setting(self, target_repo, settings_key, default=False):
340 settings_model = VcsSettingsModel(repo=target_repo)
340 settings_model = VcsSettingsModel(repo=target_repo)
341 settings = settings_model.get_repo_settings_inherited()
341 settings = settings_model.get_repo_settings_inherited()
342 return settings.get(settings_key, default)
342 return settings.get(settings_key, default)
343
343
344 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
344 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
345 log.debug('Looking for README file at path %s', path)
345 log.debug('Looking for README file at path %s', path)
346 if commit_id:
346 if commit_id:
347 landing_commit_id = commit_id
347 landing_commit_id = commit_id
348 else:
348 else:
349 landing_commit = db_repo.get_landing_commit()
349 landing_commit = db_repo.get_landing_commit()
350 if isinstance(landing_commit, EmptyCommit):
350 if isinstance(landing_commit, EmptyCommit):
351 return None, None
351 return None, None
352 landing_commit_id = landing_commit.raw_id
352 landing_commit_id = landing_commit.raw_id
353
353
354 cache_namespace_uid = f'repo.{db_repo.repo_id}'
354 cache_namespace_uid = f'repo.{db_repo.repo_id}'
355 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid, use_async_runner=True)
355 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid, use_async_runner=True)
356 start = time.time()
356 start = time.time()
357
357
358 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
358 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
359 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
359 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
360 readme_data = None
360 readme_data = None
361 readme_filename = None
361 readme_filename = None
362
362
363 commit = db_repo.get_commit(_commit_id)
363 commit = db_repo.get_commit(_commit_id)
364 log.debug("Searching for a README file at commit %s.", _commit_id)
364 log.debug("Searching for a README file at commit %s.", _commit_id)
365 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
365 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
366
366
367 if readme_node:
367 if readme_node:
368 log.debug('Found README node: %s', readme_node)
368 log.debug('Found README node: %s', readme_node)
369 relative_urls = {
369 relative_urls = {
370 'raw': h.route_path(
370 'raw': h.route_path(
371 'repo_file_raw', repo_name=_repo_name,
371 'repo_file_raw', repo_name=_repo_name,
372 commit_id=commit.raw_id, f_path=readme_node.path),
372 commit_id=commit.raw_id, f_path=readme_node.path),
373 'standard': h.route_path(
373 'standard': h.route_path(
374 'repo_files', repo_name=_repo_name,
374 'repo_files', repo_name=_repo_name,
375 commit_id=commit.raw_id, f_path=readme_node.path),
375 commit_id=commit.raw_id, f_path=readme_node.path),
376 }
376 }
377
377
378 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
378 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
379 readme_filename = readme_node.str_path
379 readme_filename = readme_node.str_path
380
380
381 return readme_data, readme_filename
381 return readme_data, readme_filename
382
382
383 readme_data, readme_filename = generate_repo_readme(
383 readme_data, readme_filename = generate_repo_readme(
384 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
384 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
385
385
386 compute_time = time.time() - start
386 compute_time = time.time() - start
387 log.debug('Repo README for path %s generated and computed in %.4fs',
387 log.debug('Repo README for path %s generated and computed in %.4fs',
388 path, compute_time)
388 path, compute_time)
389 return readme_data, readme_filename
389 return readme_data, readme_filename
390
390
391 def _render_readme_or_none(self, commit, readme_node, relative_urls):
391 def _render_readme_or_none(self, commit, readme_node, relative_urls):
392 log.debug('Found README file `%s` rendering...', readme_node.path)
392 log.debug('Found README file `%s` rendering...', readme_node.path)
393 renderer = MarkupRenderer()
393 renderer = MarkupRenderer()
394 try:
394 try:
395 html_source = renderer.render(
395 html_source = renderer.render(
396 readme_node.str_content, filename=readme_node.path)
396 readme_node.str_content, filename=readme_node.path)
397 if relative_urls:
397 if relative_urls:
398 return relative_links(html_source, relative_urls)
398 return relative_links(html_source, relative_urls)
399 return html_source
399 return html_source
400 except Exception:
400 except Exception:
401 log.exception("Exception while trying to render the README")
401 log.exception("Exception while trying to render the README")
402
402
403 def get_recache_flag(self):
403 def get_recache_flag(self):
404 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
404 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
405 flag_val = self.request.GET.get(flag_name)
405 flag_val = self.request.GET.get(flag_name)
406 if str2bool(flag_val):
406 if str2bool(flag_val):
407 return True
407 return True
408 return False
408 return False
409
409
410 def get_commit_preload_attrs(cls):
410 def get_commit_preload_attrs(cls):
411 pre_load = ['author', 'branch', 'date', 'message', 'parents',
411 pre_load = ['author', 'branch', 'date', 'message', 'parents',
412 'obsolete', 'phase', 'hidden']
412 'obsolete', 'phase', 'hidden']
413 return pre_load
413 return pre_load
414
414
415
415
416 class PathFilter(object):
416 class PathFilter(object):
417
417
418 # Expects and instance of BasePathPermissionChecker or None
418 # Expects and instance of BasePathPermissionChecker or None
419 def __init__(self, permission_checker):
419 def __init__(self, permission_checker):
420 self.permission_checker = permission_checker
420 self.permission_checker = permission_checker
421
421
422 def assert_path_permissions(self, path):
422 def assert_path_permissions(self, path):
423 if self.path_access_allowed(path):
423 if self.path_access_allowed(path):
424 return path
424 return path
425 raise HTTPForbidden()
425 raise HTTPForbidden()
426
426
427 def path_access_allowed(self, path):
427 def path_access_allowed(self, path):
428 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
428 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
429 if self.permission_checker:
429 if self.permission_checker:
430 has_access = path and self.permission_checker.has_access(path)
430 has_access = path and self.permission_checker.has_access(path)
431 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
431 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
432 return has_access
432 return has_access
433
433
434 log.debug('ACL permissions checker not enabled, skipping...')
434 log.debug('ACL permissions checker not enabled, skipping...')
435 return True
435 return True
436
436
437 def filter_patchset(self, patchset):
437 def filter_patchset(self, patchset):
438 if not self.permission_checker or not patchset:
438 if not self.permission_checker or not patchset:
439 return patchset, False
439 return patchset, False
440 had_filtered = False
440 had_filtered = False
441 filtered_patchset = []
441 filtered_patchset = []
442 for patch in patchset:
442 for patch in patchset:
443 filename = patch.get('filename', None)
443 filename = patch.get('filename', None)
444 if not filename or self.permission_checker.has_access(filename):
444 if not filename or self.permission_checker.has_access(filename):
445 filtered_patchset.append(patch)
445 filtered_patchset.append(patch)
446 else:
446 else:
447 had_filtered = True
447 had_filtered = True
448 if had_filtered:
448 if had_filtered:
449 if isinstance(patchset, diffs.LimitedDiffContainer):
449 if isinstance(patchset, diffs.LimitedDiffContainer):
450 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
450 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
451 return filtered_patchset, True
451 return filtered_patchset, True
452 else:
452 else:
453 return patchset, False
453 return patchset, False
454
454
455 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
455 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
456
456
457 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
457 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
458 result = diffset.render_patchset(
458 result = diffset.render_patchset(
459 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
459 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
460 result.has_hidden_changes = has_hidden_changes
460 result.has_hidden_changes = has_hidden_changes
461 return result
461 return result
462
462
463 def get_raw_patch(self, diff_processor):
463 def get_raw_patch(self, diff_processor):
464 if self.permission_checker is None:
464 if self.permission_checker is None:
465 return diff_processor.as_raw()
465 return diff_processor.as_raw()
466 elif self.permission_checker.has_full_access:
466 elif self.permission_checker.has_full_access:
467 return diff_processor.as_raw()
467 return diff_processor.as_raw()
468 else:
468 else:
469 return '# Repository has user-specific filters, raw patch generation is disabled.'
469 return '# Repository has user-specific filters, raw patch generation is disabled.'
470
470
471 @property
471 @property
472 def is_enabled(self):
472 def is_enabled(self):
473 return self.permission_checker is not None
473 return self.permission_checker is not None
474
474
475
475
476 class RepoGroupAppView(BaseAppView):
476 class RepoGroupAppView(BaseAppView):
477 def __init__(self, context, request):
477 def __init__(self, context, request):
478 super().__init__(context, request)
478 super().__init__(context, request)
479 self.db_repo_group = request.db_repo_group
479 self.db_repo_group = request.db_repo_group
480 self.db_repo_group_name = self.db_repo_group.group_name
480 self.db_repo_group_name = self.db_repo_group.group_name
481
481
482 def _get_local_tmpl_context(self, include_app_defaults=True):
482 def _get_local_tmpl_context(self, include_app_defaults=True):
483 _ = self.request.translate
483 _ = self.request.translate
484 c = super()._get_local_tmpl_context(
484 c = super()._get_local_tmpl_context(
485 include_app_defaults=include_app_defaults)
485 include_app_defaults=include_app_defaults)
486 c.repo_group = self.db_repo_group
486 c.repo_group = self.db_repo_group
487 return c
487 return c
488
488
489 def _revoke_perms_on_yourself(self, form_result):
489 def _revoke_perms_on_yourself(self, form_result):
490 _updates = [u for u in form_result['perm_updates'] if self._rhodecode_user.user_id == int(u[0])]
490 _updates = [u for u in form_result['perm_updates'] if self._rhodecode_user.user_id == int(u[0])]
491 _additions = [u for u in form_result['perm_additions'] if self._rhodecode_user.user_id == int(u[0])]
491 _additions = [u for u in form_result['perm_additions'] if self._rhodecode_user.user_id == int(u[0])]
492 _deletions = [u for u in form_result['perm_deletions'] if self._rhodecode_user.user_id == int(u[0])]
492 _deletions = [u for u in form_result['perm_deletions'] if self._rhodecode_user.user_id == int(u[0])]
493 admin_perm = 'group.admin'
493 admin_perm = 'group.admin'
494 if _updates and _updates[0][1] != admin_perm or \
494 if _updates and _updates[0][1] != admin_perm or \
495 _additions and _additions[0][1] != admin_perm or \
495 _additions and _additions[0][1] != admin_perm or \
496 _deletions and _deletions[0][1] != admin_perm:
496 _deletions and _deletions[0][1] != admin_perm:
497 return True
497 return True
498 return False
498 return False
499
499
500
500
501 class UserGroupAppView(BaseAppView):
501 class UserGroupAppView(BaseAppView):
502 def __init__(self, context, request):
502 def __init__(self, context, request):
503 super().__init__(context, request)
503 super().__init__(context, request)
504 self.db_user_group = request.db_user_group
504 self.db_user_group = request.db_user_group
505 self.db_user_group_name = self.db_user_group.users_group_name
505 self.db_user_group_name = self.db_user_group.users_group_name
506
506
507
507
508 class UserAppView(BaseAppView):
508 class UserAppView(BaseAppView):
509 def __init__(self, context, request):
509 def __init__(self, context, request):
510 super().__init__(context, request)
510 super().__init__(context, request)
511 self.db_user = request.db_user
511 self.db_user = request.db_user
512 self.db_user_id = self.db_user.user_id
512 self.db_user_id = self.db_user.user_id
513
513
514 _ = self.request.translate
514 _ = self.request.translate
515 if not request.db_user_supports_default:
515 if not request.db_user_supports_default:
516 if self.db_user.username == User.DEFAULT_USER:
516 if self.db_user.username == User.DEFAULT_USER:
517 h.flash(_("Editing user `{}` is disabled.".format(
517 h.flash(_("Editing user `{}` is disabled.".format(
518 User.DEFAULT_USER)), category='warning')
518 User.DEFAULT_USER)), category='warning')
519 raise HTTPFound(h.route_path('users'))
519 raise HTTPFound(h.route_path('users'))
520
520
521
521
522 class DataGridAppView(object):
522 class DataGridAppView(object):
523 """
523 """
524 Common class to have re-usable grid rendering components
524 Common class to have re-usable grid rendering components
525 """
525 """
526
526
527 def _extract_ordering(self, request, column_map=None):
527 def _extract_ordering(self, request, column_map=None):
528 column_map = column_map or {}
528 column_map = column_map or {}
529 column_index = safe_int(request.GET.get('order[0][column]'))
529 column_index = safe_int(request.GET.get('order[0][column]'))
530 order_dir = request.GET.get(
530 order_dir = request.GET.get(
531 'order[0][dir]', 'desc')
531 'order[0][dir]', 'desc')
532 order_by = request.GET.get(
532 order_by = request.GET.get(
533 'columns[%s][data][sort]' % column_index, 'name_raw')
533 'columns[%s][data][sort]' % column_index, 'name_raw')
534
534
535 # translate datatable to DB columns
535 # translate datatable to DB columns
536 order_by = column_map.get(order_by) or order_by
536 order_by = column_map.get(order_by) or order_by
537
537
538 search_q = request.GET.get('search[value]')
538 search_q = request.GET.get('search[value]')
539 return search_q, order_by, order_dir
539 return search_q, order_by, order_dir
540
540
541 def _extract_chunk(self, request):
541 def _extract_chunk(self, request):
542 start = safe_int(request.GET.get('start'), 0)
542 start = safe_int(request.GET.get('start'), 0)
543 length = safe_int(request.GET.get('length'), 25)
543 length = safe_int(request.GET.get('length'), 25)
544 draw = safe_int(request.GET.get('draw'))
544 draw = safe_int(request.GET.get('draw'))
545 return draw, start, length
545 return draw, start, length
546
546
547 def _get_order_col(self, order_by, model):
547 def _get_order_col(self, order_by, model):
548 if isinstance(order_by, str):
548 if isinstance(order_by, str):
549 try:
549 try:
550 return operator.attrgetter(order_by)(model)
550 return operator.attrgetter(order_by)(model)
551 except AttributeError:
551 except AttributeError:
552 return None
552 return None
553 else:
553 else:
554 return order_by
554 return order_by
555
555
556
556
557 class BaseReferencesView(RepoAppView):
557 class BaseReferencesView(RepoAppView):
558 """
558 """
559 Base for reference view for branches, tags and bookmarks.
559 Base for reference view for branches, tags and bookmarks.
560 """
560 """
561 def load_default_context(self):
561 def load_default_context(self):
562 c = self._get_local_tmpl_context()
562 c = self._get_local_tmpl_context()
563 return c
563 return c
564
564
565 def load_refs_context(self, ref_items, partials_template):
565 def load_refs_context(self, ref_items, partials_template):
566 _render = self.request.get_partial_renderer(partials_template)
566 _render = self.request.get_partial_renderer(partials_template)
567 pre_load = ["author", "date", "message", "parents"]
567 pre_load = ["author", "date", "message", "parents"]
568
568
569 is_svn = h.is_svn(self.rhodecode_vcs_repo)
569 is_svn = h.is_svn(self.rhodecode_vcs_repo)
570 is_hg = h.is_hg(self.rhodecode_vcs_repo)
570 is_hg = h.is_hg(self.rhodecode_vcs_repo)
571
571
572 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
572 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
573
573
574 closed_refs = {}
574 closed_refs = {}
575 if is_hg:
575 if is_hg:
576 closed_refs = self.rhodecode_vcs_repo.branches_closed
576 closed_refs = self.rhodecode_vcs_repo.branches_closed
577
577
578 data = []
578 data = []
579 for ref_name, commit_id in ref_items:
579 for ref_name, commit_id in ref_items:
580 commit = self.rhodecode_vcs_repo.get_commit(
580 commit = self.rhodecode_vcs_repo.get_commit(
581 commit_id=commit_id, pre_load=pre_load)
581 commit_id=commit_id, pre_load=pre_load)
582 closed = ref_name in closed_refs
582 closed = ref_name in closed_refs
583
583
584 # TODO: johbo: Unify generation of reference links
584 # TODO: johbo: Unify generation of reference links
585 use_commit_id = '/' in ref_name or is_svn
585 use_commit_id = '/' in ref_name or is_svn
586
586
587 if use_commit_id:
587 if use_commit_id:
588 files_url = h.route_path(
588 files_url = h.route_path(
589 'repo_files',
589 'repo_files',
590 repo_name=self.db_repo_name,
590 repo_name=self.db_repo_name,
591 f_path=ref_name if is_svn else '',
591 f_path=ref_name if is_svn else '',
592 commit_id=commit_id,
592 commit_id=commit_id,
593 _query=dict(at=ref_name)
593 _query=dict(at=ref_name)
594 )
594 )
595
595
596 else:
596 else:
597 files_url = h.route_path(
597 files_url = h.route_path(
598 'repo_files',
598 'repo_files',
599 repo_name=self.db_repo_name,
599 repo_name=self.db_repo_name,
600 f_path=ref_name if is_svn else '',
600 f_path=ref_name if is_svn else '',
601 commit_id=ref_name,
601 commit_id=ref_name,
602 _query=dict(at=ref_name)
602 _query=dict(at=ref_name)
603 )
603 )
604
604
605 data.append({
605 data.append({
606 "name": _render('name', ref_name, files_url, closed),
606 "name": _render('name', ref_name, files_url, closed),
607 "name_raw": ref_name,
607 "name_raw": ref_name,
608 "date": _render('date', commit.date),
608 "date": _render('date', commit.date),
609 "date_raw": datetime_to_time(commit.date),
609 "date_raw": datetime_to_time(commit.date),
610 "author": _render('author', commit.author),
610 "author": _render('author', commit.author),
611 "commit": _render(
611 "commit": _render(
612 'commit', commit.message, commit.raw_id, commit.idx),
612 'commit', commit.message, commit.raw_id, commit.idx),
613 "commit_raw": commit.idx,
613 "commit_raw": commit.idx,
614 "compare": _render(
614 "compare": _render(
615 'compare', format_ref_id(ref_name, commit.raw_id)),
615 'compare', format_ref_id(ref_name, commit.raw_id)),
616 })
616 })
617
617
618 return data
618 return data
619
619
620
620
621 class RepoRoutePredicate(object):
621 class RepoRoutePredicate(object):
622 def __init__(self, val, config):
622 def __init__(self, val, config):
623 self.val = val
623 self.val = val
624
624
625 def text(self):
625 def text(self):
626 return f'repo_route = {self.val}'
626 return f'repo_route = {self.val}'
627
627
628 phash = text
628 phash = text
629
629
630 def __call__(self, info, request):
630 def __call__(self, info, request):
631 if hasattr(request, 'vcs_call'):
631 if hasattr(request, 'vcs_call'):
632 # skip vcs calls
632 # skip vcs calls
633 return
633 return
634
634
635 repo_name = info['match']['repo_name']
635 repo_name = info['match']['repo_name']
636
636
637 repo_name_parts = repo_name.split('/')
637 repo_name_parts = repo_name.split('/')
638 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
638 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
639
639
640 if repo_name_parts != repo_slugs:
640 if repo_name_parts != repo_slugs:
641 # short-skip if the repo-name doesn't follow slug rule
641 # short-skip if the repo-name doesn't follow slug rule
642 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
642 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
643 return False
643 return False
644
644
645 repo_model = repo.RepoModel()
645 repo_model = repo.RepoModel()
646
646
647 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
647 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
648
648
649 def redirect_if_creating(route_info, db_repo):
649 def redirect_if_creating(route_info, db_repo):
650 skip_views = ['edit_repo_advanced_delete']
650 skip_views = ['edit_repo_advanced_delete']
651 route = route_info['route']
651 route = route_info['route']
652 # we should skip delete view so we can actually "remove" repositories
652 # we should skip delete view so we can actually "remove" repositories
653 # if they get stuck in creating state.
653 # if they get stuck in creating state.
654 if route.name in skip_views:
654 if route.name in skip_views:
655 return
655 return
656
656
657 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
657 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
658 repo_creating_url = request.route_path(
658 repo_creating_url = request.route_path(
659 'repo_creating', repo_name=db_repo.repo_name)
659 'repo_creating', repo_name=db_repo.repo_name)
660 raise HTTPFound(repo_creating_url)
660 raise HTTPFound(repo_creating_url)
661
661
662 if by_name_match:
662 if by_name_match:
663 # register this as request object we can re-use later
663 # register this as request object we can re-use later
664 request.db_repo = by_name_match
664 request.db_repo = by_name_match
665 request.db_repo_name = request.db_repo.repo_name
665 request.db_repo_name = request.db_repo.repo_name
666
666
667 redirect_if_creating(info, by_name_match)
667 redirect_if_creating(info, by_name_match)
668 return True
668 return True
669
669
670 by_id_match = repo_model.get_repo_by_id(repo_name)
670 by_id_match = repo_model.get_repo_by_id(repo_name)
671 if by_id_match:
671 if by_id_match:
672 request.db_repo = by_id_match
672 request.db_repo = by_id_match
673 request.db_repo_name = request.db_repo.repo_name
673 request.db_repo_name = request.db_repo.repo_name
674 redirect_if_creating(info, by_id_match)
674 redirect_if_creating(info, by_id_match)
675 return True
675 return True
676
676
677 return False
677 return False
678
678
679
679
680 class RepoForbidArchivedRoutePredicate(object):
680 class RepoForbidArchivedRoutePredicate(object):
681 def __init__(self, val, config):
681 def __init__(self, val, config):
682 self.val = val
682 self.val = val
683
683
684 def text(self):
684 def text(self):
685 return f'repo_forbid_archived = {self.val}'
685 return f'repo_forbid_archived = {self.val}'
686
686
687 phash = text
687 phash = text
688
688
689 def __call__(self, info, request):
689 def __call__(self, info, request):
690 _ = request.translate
690 _ = request.translate
691 rhodecode_db_repo = request.db_repo
691 rhodecode_db_repo = request.db_repo
692
692
693 log.debug(
693 log.debug(
694 '%s checking if archived flag for repo for %s',
694 '%s checking if archived flag for repo for %s',
695 self.__class__.__name__, rhodecode_db_repo.repo_name)
695 self.__class__.__name__, rhodecode_db_repo.repo_name)
696
696
697 if rhodecode_db_repo.archived:
697 if rhodecode_db_repo.archived:
698 log.warning('Current view is not supported for archived repo:%s',
698 log.warning('Current view is not supported for archived repo:%s',
699 rhodecode_db_repo.repo_name)
699 rhodecode_db_repo.repo_name)
700
700
701 h.flash(
701 h.flash(
702 h.literal(_('Action not supported for archived repository.')),
702 h.literal(_('Action not supported for archived repository.')),
703 category='warning')
703 category='warning')
704 summary_url = request.route_path(
704 summary_url = request.route_path(
705 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
705 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
706 raise HTTPFound(summary_url)
706 raise HTTPFound(summary_url)
707 return True
707 return True
708
708
709
709
710 class RepoTypeRoutePredicate(object):
710 class RepoTypeRoutePredicate(object):
711 def __init__(self, val, config):
711 def __init__(self, val, config):
712 self.val = val or ['hg', 'git', 'svn']
712 self.val = val or ['hg', 'git', 'svn']
713
713
714 def text(self):
714 def text(self):
715 return f'repo_accepted_type = {self.val}'
715 return f'repo_accepted_type = {self.val}'
716
716
717 phash = text
717 phash = text
718
718
719 def __call__(self, info, request):
719 def __call__(self, info, request):
720 if hasattr(request, 'vcs_call'):
720 if hasattr(request, 'vcs_call'):
721 # skip vcs calls
721 # skip vcs calls
722 return
722 return
723
723
724 rhodecode_db_repo = request.db_repo
724 rhodecode_db_repo = request.db_repo
725
725
726 log.debug(
726 log.debug(
727 '%s checking repo type for %s in %s',
727 '%s checking repo type for %s in %s',
728 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
728 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
729
729
730 if rhodecode_db_repo.repo_type in self.val:
730 if rhodecode_db_repo.repo_type in self.val:
731 return True
731 return True
732 else:
732 else:
733 log.warning('Current view is not supported for repo type:%s',
733 log.warning('Current view is not supported for repo type:%s',
734 rhodecode_db_repo.repo_type)
734 rhodecode_db_repo.repo_type)
735 return False
735 return False
736
736
737
737
738 class RepoGroupRoutePredicate(object):
738 class RepoGroupRoutePredicate(object):
739 def __init__(self, val, config):
739 def __init__(self, val, config):
740 self.val = val
740 self.val = val
741
741
742 def text(self):
742 def text(self):
743 return f'repo_group_route = {self.val}'
743 return f'repo_group_route = {self.val}'
744
744
745 phash = text
745 phash = text
746
746
747 def __call__(self, info, request):
747 def __call__(self, info, request):
748 if hasattr(request, 'vcs_call'):
748 if hasattr(request, 'vcs_call'):
749 # skip vcs calls
749 # skip vcs calls
750 return
750 return
751
751
752 repo_group_name = info['match']['repo_group_name']
752 repo_group_name = info['match']['repo_group_name']
753
753
754 repo_group_name_parts = repo_group_name.split('/')
754 repo_group_name_parts = repo_group_name.split('/')
755 repo_group_slugs = [x for x in [repo_name_slug(x) for x in repo_group_name_parts]]
755 repo_group_slugs = [x for x in [repo_name_slug(x) for x in repo_group_name_parts]]
756 if repo_group_name_parts != repo_group_slugs:
756 if repo_group_name_parts != repo_group_slugs:
757 # short-skip if the repo-name doesn't follow slug rule
757 # short-skip if the repo-name doesn't follow slug rule
758 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
758 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
759 return False
759 return False
760
760
761 repo_group_model = repo_group.RepoGroupModel()
761 repo_group_model = repo_group.RepoGroupModel()
762 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
762 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
763
763
764 if by_name_match:
764 if by_name_match:
765 # register this as request object we can re-use later
765 # register this as request object we can re-use later
766 request.db_repo_group = by_name_match
766 request.db_repo_group = by_name_match
767 request.db_repo_group_name = request.db_repo_group.group_name
767 request.db_repo_group_name = request.db_repo_group.group_name
768 return True
768 return True
769
769
770 return False
770 return False
771
771
772
772
773 class UserGroupRoutePredicate(object):
773 class UserGroupRoutePredicate(object):
774 def __init__(self, val, config):
774 def __init__(self, val, config):
775 self.val = val
775 self.val = val
776
776
777 def text(self):
777 def text(self):
778 return f'user_group_route = {self.val}'
778 return f'user_group_route = {self.val}'
779
779
780 phash = text
780 phash = text
781
781
782 def __call__(self, info, request):
782 def __call__(self, info, request):
783 if hasattr(request, 'vcs_call'):
783 if hasattr(request, 'vcs_call'):
784 # skip vcs calls
784 # skip vcs calls
785 return
785 return
786
786
787 user_group_id = info['match']['user_group_id']
787 user_group_id = info['match']['user_group_id']
788 user_group_model = user_group.UserGroup()
788 user_group_model = user_group.UserGroup()
789 by_id_match = user_group_model.get(user_group_id, cache=False)
789 by_id_match = user_group_model.get(user_group_id, cache=False)
790
790
791 if by_id_match:
791 if by_id_match:
792 # register this as request object we can re-use later
792 # register this as request object we can re-use later
793 request.db_user_group = by_id_match
793 request.db_user_group = by_id_match
794 return True
794 return True
795
795
796 return False
796 return False
797
797
798
798
799 class UserRoutePredicateBase(object):
799 class UserRoutePredicateBase(object):
800 supports_default = None
800 supports_default = None
801
801
802 def __init__(self, val, config):
802 def __init__(self, val, config):
803 self.val = val
803 self.val = val
804
804
805 def text(self):
805 def text(self):
806 raise NotImplementedError()
806 raise NotImplementedError()
807
807
808 def __call__(self, info, request):
808 def __call__(self, info, request):
809 if hasattr(request, 'vcs_call'):
809 if hasattr(request, 'vcs_call'):
810 # skip vcs calls
810 # skip vcs calls
811 return
811 return
812
812
813 user_id = info['match']['user_id']
813 user_id = info['match']['user_id']
814 user_model = user.User()
814 user_model = user.User()
815 by_id_match = user_model.get(user_id, cache=False)
815 by_id_match = user_model.get(user_id, cache=False)
816
816
817 if by_id_match:
817 if by_id_match:
818 # register this as request object we can re-use later
818 # register this as request object we can re-use later
819 request.db_user = by_id_match
819 request.db_user = by_id_match
820 request.db_user_supports_default = self.supports_default
820 request.db_user_supports_default = self.supports_default
821 return True
821 return True
822
822
823 return False
823 return False
824
824
825
825
826 class UserRoutePredicate(UserRoutePredicateBase):
826 class UserRoutePredicate(UserRoutePredicateBase):
827 supports_default = False
827 supports_default = False
828
828
829 def text(self):
829 def text(self):
830 return f'user_route = {self.val}'
830 return f'user_route = {self.val}'
831
831
832 phash = text
832 phash = text
833
833
834
834
835 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
835 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
836 supports_default = True
836 supports_default = True
837
837
838 def text(self):
838 def text(self):
839 return f'user_with_default_route = {self.val}'
839 return f'user_with_default_route = {self.val}'
840
840
841 phash = text
841 phash = text
842
842
843
843
844 def includeme(config):
844 def includeme(config):
845 config.add_route_predicate(
845 config.add_route_predicate(
846 'repo_route', RepoRoutePredicate)
846 'repo_route', RepoRoutePredicate)
847 config.add_route_predicate(
847 config.add_route_predicate(
848 'repo_accepted_types', RepoTypeRoutePredicate)
848 'repo_accepted_types', RepoTypeRoutePredicate)
849 config.add_route_predicate(
849 config.add_route_predicate(
850 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
850 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
851 config.add_route_predicate(
851 config.add_route_predicate(
852 'repo_group_route', RepoGroupRoutePredicate)
852 'repo_group_route', RepoGroupRoutePredicate)
853 config.add_route_predicate(
853 config.add_route_predicate(
854 'user_group_route', UserGroupRoutePredicate)
854 'user_group_route', UserGroupRoutePredicate)
855 config.add_route_predicate(
855 config.add_route_predicate(
856 'user_route_with_default', UserRouteWithDefaultPredicate)
856 'user_route_with_default', UserRouteWithDefaultPredicate)
857 config.add_route_predicate(
857 config.add_route_predicate(
858 'user_route', UserRoutePredicate)
858 'user_route', UserRoutePredicate)
@@ -1,154 +1,152 b''
1
1
2 import dataclasses
2 import dataclasses
3 # Copyright (C) 2016-2023 RhodeCode GmbH
3 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from zope.interface import implementer
25 from zope.interface import implementer
26
26
27 from rhodecode.apps._base.interfaces import IAdminNavigationRegistry
27 from rhodecode.apps._base.interfaces import IAdminNavigationRegistry
28 from rhodecode.lib.utils2 import str2bool
28 from rhodecode.lib.utils2 import str2bool
29 from rhodecode.translation import _
29 from rhodecode.translation import _
30
30
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 @dataclasses.dataclass
35 @dataclasses.dataclass
36 class NavListEntry:
36 class NavListEntry:
37 key: str
37 key: str
38 name: str
38 name: str
39 url: str
39 url: str
40 active_list: list
40 active_list: list
41
41
42
42
43 class NavEntry(object):
43 class NavEntry(object):
44 """
44 """
45 Represents an entry in the admin navigation.
45 Represents an entry in the admin navigation.
46
46
47 :param key: Unique identifier used to store reference in an OrderedDict.
47 :param key: Unique identifier used to store reference in an OrderedDict.
48 :param name: Display name, usually a translation string.
48 :param name: Display name, usually a translation string.
49 :param view_name: Name of the view, used generate the URL.
49 :param view_name: Name of the view, used generate the URL.
50 :param active_list: list of urls that we select active for this element
50 :param active_list: list of urls that we select active for this element
51 """
51 """
52
52
53 def __init__(self, key, name, view_name, active_list=None):
53 def __init__(self, key, name, view_name, active_list=None):
54 self.key = key
54 self.key = key
55 self.name = name
55 self.name = name
56 self.view_name = view_name
56 self.view_name = view_name
57 self._active_list = active_list or []
57 self._active_list = active_list or []
58
58
59 def generate_url(self, request):
59 def generate_url(self, request):
60 return request.route_path(self.view_name)
60 return request.route_path(self.view_name)
61
61
62 def get_localized_name(self, request):
62 def get_localized_name(self, request):
63 return request.translate(self.name)
63 return request.translate(self.name)
64
64
65 @property
65 @property
66 def active_list(self):
66 def active_list(self):
67 active_list = [self.key]
67 active_list = [self.key]
68 if self._active_list:
68 if self._active_list:
69 active_list = self._active_list
69 active_list = self._active_list
70 return active_list
70 return active_list
71
71
72
72
73 @implementer(IAdminNavigationRegistry)
73 @implementer(IAdminNavigationRegistry)
74 class NavigationRegistry(object):
74 class NavigationRegistry(object):
75
75
76 _base_entries = [
76 _base_entries = [
77 NavEntry('global', _('Global'),
77 NavEntry('global', _('Global'),
78 'admin_settings_global'),
78 'admin_settings_global'),
79 NavEntry('vcs', _('VCS'),
79 NavEntry('vcs', _('VCS'),
80 'admin_settings_vcs'),
80 'admin_settings_vcs'),
81 NavEntry('visual', _('Visual'),
81 NavEntry('visual', _('Visual'),
82 'admin_settings_visual'),
82 'admin_settings_visual'),
83 NavEntry('mapping', _('Remap and Rescan'),
83 NavEntry('mapping', _('Remap and Rescan'),
84 'admin_settings_mapping'),
84 'admin_settings_mapping'),
85 NavEntry('issuetracker', _('Issue Tracker'),
85 NavEntry('issuetracker', _('Issue Tracker'),
86 'admin_settings_issuetracker'),
86 'admin_settings_issuetracker'),
87 NavEntry('email', _('Email'),
87 NavEntry('email', _('Email'),
88 'admin_settings_email'),
88 'admin_settings_email'),
89 NavEntry('hooks', _('Hooks'),
89 NavEntry('hooks', _('Hooks'),
90 'admin_settings_hooks'),
90 'admin_settings_hooks'),
91 NavEntry('search', _('Full Text Search'),
91 NavEntry('search', _('Full Text Search'),
92 'admin_settings_search'),
92 'admin_settings_search'),
93 NavEntry('system', _('System Info'),
93 NavEntry('system', _('System Info'),
94 'admin_settings_system'),
94 'admin_settings_system'),
95 NavEntry('exceptions', _('Exceptions Tracker'),
95 NavEntry('exceptions', _('Exceptions Tracker'),
96 'admin_settings_exception_tracker',
96 'admin_settings_exception_tracker',
97 active_list=['exceptions', 'exceptions_browse']),
97 active_list=['exceptions', 'exceptions_browse']),
98 NavEntry('process_management', _('Processes'),
98 NavEntry('process_management', _('Processes'),
99 'admin_settings_process_management'),
99 'admin_settings_process_management'),
100 NavEntry('sessions', _('User Sessions'),
100 NavEntry('sessions', _('User Sessions'),
101 'admin_settings_sessions'),
101 'admin_settings_sessions'),
102 NavEntry('open_source', _('Open Source Licenses'),
102 NavEntry('open_source', _('Open Source Licenses'),
103 'admin_settings_open_source'),
103 'admin_settings_open_source'),
104 NavEntry('automation', _('Automation'),
105 'admin_settings_automation')
106 ]
104 ]
107
105
108 _labs_entry = NavEntry('labs', _('Labs'),
106 _labs_entry = NavEntry('labs', _('Labs'),
109 'admin_settings_labs')
107 'admin_settings_labs')
110
108
111 def __init__(self, labs_active=False):
109 def __init__(self, labs_active=False):
112 self._registered_entries = collections.OrderedDict()
110 self._registered_entries = collections.OrderedDict()
113 for item in self.__class__._base_entries:
111 for item in self.__class__._base_entries:
114 self.add_entry(item)
112 self.add_entry(item)
115
113
116 if labs_active:
114 if labs_active:
117 self.add_entry(self._labs_entry)
115 self.add_entry(self._labs_entry)
118
116
119 def add_entry(self, entry):
117 def add_entry(self, entry):
120 self._registered_entries[entry.key] = entry
118 self._registered_entries[entry.key] = entry
121
119
122 def get_navlist(self, request):
120 def get_navlist(self, request):
123 nav_list = [
121 nav_list = [
124 NavListEntry(i.key, i.get_localized_name(request),
122 NavListEntry(i.key, i.get_localized_name(request),
125 i.generate_url(request), i.active_list)
123 i.generate_url(request), i.active_list)
126 for i in self._registered_entries.values()
124 for i in self._registered_entries.values()
127 ]
125 ]
128 return nav_list
126 return nav_list
129
127
130
128
131 def navigation_registry(request, registry=None):
129 def navigation_registry(request, registry=None):
132 """
130 """
133 Helper that returns the admin navigation registry.
131 Helper that returns the admin navigation registry.
134 """
132 """
135 pyramid_registry = registry or request.registry
133 pyramid_registry = registry or request.registry
136 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
134 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
137 return nav_registry
135 return nav_registry
138
136
139
137
140 def navigation_list(request):
138 def navigation_list(request):
141 """
139 """
142 Helper that returns the admin navigation as list of NavListEntry objects.
140 Helper that returns the admin navigation as list of NavListEntry objects.
143 """
141 """
144 return navigation_registry(request).get_navlist(request)
142 return navigation_registry(request).get_navlist(request)
145
143
146
144
147 def includeme(config):
145 def includeme(config):
148 # Create admin navigation registry and add it to the pyramid registry.
146 # Create admin navigation registry and add it to the pyramid registry.
149 settings = config.get_settings()
147 settings = config.get_settings()
150 labs_active = str2bool(settings.get('labs_settings_active', False))
148 labs_active = str2bool(settings.get('labs_settings_active', False))
151 navigation_registry_instance = NavigationRegistry(labs_active=labs_active)
149 navigation_registry_instance = NavigationRegistry(labs_active=labs_active)
152 config.registry.registerUtility(navigation_registry_instance)
150 config.registry.registerUtility(navigation_registry_instance)
153 log.debug('Created new navigation instance, %s', navigation_registry_instance)
151 log.debug('Created new navigation instance, %s', navigation_registry_instance)
154
152
@@ -1,1082 +1,1093 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 from rhodecode.apps._base import ADMIN_PREFIX
20 from rhodecode.apps._base import ADMIN_PREFIX
21 from rhodecode.apps._base.navigation import includeme as nav_includeme
22 from rhodecode.apps.admin.views.main_views import AdminMainView
21
23
22
24
23 def admin_routes(config):
25 def admin_routes(config):
24 """
26 """
25 Admin prefixed routes
27 Admin prefixed routes
26 """
28 """
27 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
29 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
28 from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
30 from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
31 from rhodecode.apps.admin.views.automation import AdminAutomationView
32 from rhodecode.apps.admin.views.scheduler import AdminSchedulerView
29 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
33 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
30 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
34 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
31 from rhodecode.apps.admin.views.main_views import AdminMainView
32 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
35 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
33 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
36 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
34 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
37 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
35 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
38 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
36 from rhodecode.apps.admin.views.repositories import AdminReposView
39 from rhodecode.apps.admin.views.repositories import AdminReposView
37 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
40 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
38 from rhodecode.apps.admin.views.settings import AdminSettingsView
41 from rhodecode.apps.admin.views.settings import AdminSettingsView
39 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
42 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
40 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
43 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
41 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
44 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
42 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
45 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
43
46
44 config.add_route(
47 config.add_route(
45 name='admin_audit_logs',
48 name='admin_audit_logs',
46 pattern='/audit_logs')
49 pattern='/audit_logs')
47 config.add_view(
50 config.add_view(
48 AdminAuditLogsView,
51 AdminAuditLogsView,
49 attr='admin_audit_logs',
52 attr='admin_audit_logs',
50 route_name='admin_audit_logs', request_method='GET',
53 route_name='admin_audit_logs', request_method='GET',
51 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
54 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
52
55
53 config.add_route(
56 config.add_route(
54 name='admin_audit_log_entry',
57 name='admin_audit_log_entry',
55 pattern='/audit_logs/{audit_log_id}')
58 pattern='/audit_logs/{audit_log_id}')
56 config.add_view(
59 config.add_view(
57 AdminAuditLogsView,
60 AdminAuditLogsView,
58 attr='admin_audit_log_entry',
61 attr='admin_audit_log_entry',
59 route_name='admin_audit_log_entry', request_method='GET',
62 route_name='admin_audit_log_entry', request_method='GET',
60 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
63 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
61
64
62 # Artifacts EE feature
65 # Artifacts EE feature
63 config.add_route(
66 config.add_route(
64 'admin_artifacts',
67 'admin_artifacts',
65 pattern=ADMIN_PREFIX + '/artifacts')
68 pattern=ADMIN_PREFIX + '/artifacts')
66 config.add_route(
69 config.add_route(
67 'admin_artifacts_show_all',
70 'admin_artifacts_show_all',
68 pattern=ADMIN_PREFIX + '/artifacts')
71 pattern=ADMIN_PREFIX + '/artifacts')
69 config.add_view(
72 config.add_view(
70 AdminArtifactsView,
73 AdminArtifactsView,
71 attr='artifacts',
74 attr='artifacts',
72 route_name='admin_artifacts', request_method='GET',
75 route_name='admin_artifacts', request_method='GET',
73 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
76 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
74 config.add_view(
77 config.add_view(
75 AdminArtifactsView,
78 AdminArtifactsView,
76 attr='artifacts',
79 attr='artifacts',
77 route_name='admin_artifacts_show_all', request_method='GET',
80 route_name='admin_artifacts_show_all', request_method='GET',
78 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
81 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
82
79 # EE views
83 # EE views
80 config.add_route(
84 config.add_route(
81 name='admin_artifacts_show_info',
85 name='admin_artifacts_show_info',
82 pattern=ADMIN_PREFIX + '/artifacts/{uid}')
86 pattern=ADMIN_PREFIX + '/artifacts/{uid}')
83 config.add_route(
87 config.add_route(
84 name='admin_artifacts_delete',
88 name='admin_artifacts_delete',
85 pattern=ADMIN_PREFIX + '/artifacts/{uid}/delete')
89 pattern=ADMIN_PREFIX + '/artifacts/{uid}/delete')
86 config.add_route(
90 config.add_route(
87 name='admin_artifacts_update',
91 name='admin_artifacts_update',
88 pattern=ADMIN_PREFIX + '/artifacts/{uid}/update')
92 pattern=ADMIN_PREFIX + '/artifacts/{uid}/update')
89
93
94 # Automation EE feature
95 config.add_route(
96 'admin_automation',
97 pattern=ADMIN_PREFIX + '/automation')
98 config.add_view(
99 AdminAutomationView,
100 attr='automation',
101 route_name='admin_automation', request_method='GET',
102 renderer='rhodecode:templates/admin/automation/automation.mako')
103
104 # Scheduler EE feature
105 config.add_route(
106 'admin_scheduler',
107 pattern=ADMIN_PREFIX + '/scheduler')
108 config.add_view(
109 AdminSchedulerView,
110 attr='scheduler',
111 route_name='admin_scheduler', request_method='GET',
112 renderer='rhodecode:templates/admin/scheduler/scheduler.mako')
113
90 config.add_route(
114 config.add_route(
91 name='admin_settings_open_source',
115 name='admin_settings_open_source',
92 pattern='/settings/open_source')
116 pattern='/settings/open_source')
93 config.add_view(
117 config.add_view(
94 OpenSourceLicensesAdminSettingsView,
118 OpenSourceLicensesAdminSettingsView,
95 attr='open_source_licenses',
119 attr='open_source_licenses',
96 route_name='admin_settings_open_source', request_method='GET',
120 route_name='admin_settings_open_source', request_method='GET',
97 renderer='rhodecode:templates/admin/settings/settings.mako')
121 renderer='rhodecode:templates/admin/settings/settings.mako')
98
122
99 config.add_route(
123 config.add_route(
100 name='admin_settings_vcs_svn_generate_cfg',
124 name='admin_settings_vcs_svn_generate_cfg',
101 pattern='/settings/vcs/svn_generate_cfg')
125 pattern='/settings/vcs/svn_generate_cfg')
102 config.add_view(
126 config.add_view(
103 AdminSvnConfigView,
127 AdminSvnConfigView,
104 attr='vcs_svn_generate_config',
128 attr='vcs_svn_generate_config',
105 route_name='admin_settings_vcs_svn_generate_cfg',
129 route_name='admin_settings_vcs_svn_generate_cfg',
106 request_method='POST', renderer='json')
130 request_method='POST', renderer='json')
107
131
108 config.add_route(
132 config.add_route(
109 name='admin_settings_system',
133 name='admin_settings_system',
110 pattern='/settings/system')
134 pattern='/settings/system')
111 config.add_view(
135 config.add_view(
112 AdminSystemInfoSettingsView,
136 AdminSystemInfoSettingsView,
113 attr='settings_system_info',
137 attr='settings_system_info',
114 route_name='admin_settings_system', request_method='GET',
138 route_name='admin_settings_system', request_method='GET',
115 renderer='rhodecode:templates/admin/settings/settings.mako')
139 renderer='rhodecode:templates/admin/settings/settings.mako')
116
140
117 config.add_route(
141 config.add_route(
118 name='admin_settings_system_update',
142 name='admin_settings_system_update',
119 pattern='/settings/system/updates')
143 pattern='/settings/system/updates')
120 config.add_view(
144 config.add_view(
121 AdminSystemInfoSettingsView,
145 AdminSystemInfoSettingsView,
122 attr='settings_system_info_check_update',
146 attr='settings_system_info_check_update',
123 route_name='admin_settings_system_update', request_method='GET',
147 route_name='admin_settings_system_update', request_method='GET',
124 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
148 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
125
149
126 config.add_route(
150 config.add_route(
127 name='admin_settings_exception_tracker',
151 name='admin_settings_exception_tracker',
128 pattern='/settings/exceptions')
152 pattern='/settings/exceptions')
129 config.add_view(
153 config.add_view(
130 ExceptionsTrackerView,
154 ExceptionsTrackerView,
131 attr='browse_exceptions',
155 attr='browse_exceptions',
132 route_name='admin_settings_exception_tracker', request_method='GET',
156 route_name='admin_settings_exception_tracker', request_method='GET',
133 renderer='rhodecode:templates/admin/settings/settings.mako')
157 renderer='rhodecode:templates/admin/settings/settings.mako')
134
158
135 config.add_route(
159 config.add_route(
136 name='admin_settings_exception_tracker_delete_all',
160 name='admin_settings_exception_tracker_delete_all',
137 pattern='/settings/exceptions_delete_all')
161 pattern='/settings/exceptions_delete_all')
138 config.add_view(
162 config.add_view(
139 ExceptionsTrackerView,
163 ExceptionsTrackerView,
140 attr='exception_delete_all',
164 attr='exception_delete_all',
141 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
165 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
142 renderer='rhodecode:templates/admin/settings/settings.mako')
166 renderer='rhodecode:templates/admin/settings/settings.mako')
143
167
144 config.add_route(
168 config.add_route(
145 name='admin_settings_exception_tracker_show',
169 name='admin_settings_exception_tracker_show',
146 pattern='/settings/exceptions/{exception_id}')
170 pattern='/settings/exceptions/{exception_id}')
147 config.add_view(
171 config.add_view(
148 ExceptionsTrackerView,
172 ExceptionsTrackerView,
149 attr='exception_show',
173 attr='exception_show',
150 route_name='admin_settings_exception_tracker_show', request_method='GET',
174 route_name='admin_settings_exception_tracker_show', request_method='GET',
151 renderer='rhodecode:templates/admin/settings/settings.mako')
175 renderer='rhodecode:templates/admin/settings/settings.mako')
152
176
153 config.add_route(
177 config.add_route(
154 name='admin_settings_exception_tracker_delete',
178 name='admin_settings_exception_tracker_delete',
155 pattern='/settings/exceptions/{exception_id}/delete')
179 pattern='/settings/exceptions/{exception_id}/delete')
156 config.add_view(
180 config.add_view(
157 ExceptionsTrackerView,
181 ExceptionsTrackerView,
158 attr='exception_delete',
182 attr='exception_delete',
159 route_name='admin_settings_exception_tracker_delete', request_method='POST',
183 route_name='admin_settings_exception_tracker_delete', request_method='POST',
160 renderer='rhodecode:templates/admin/settings/settings.mako')
184 renderer='rhodecode:templates/admin/settings/settings.mako')
161
185
162 config.add_route(
186 config.add_route(
163 name='admin_settings_sessions',
187 name='admin_settings_sessions',
164 pattern='/settings/sessions')
188 pattern='/settings/sessions')
165 config.add_view(
189 config.add_view(
166 AdminSessionSettingsView,
190 AdminSessionSettingsView,
167 attr='settings_sessions',
191 attr='settings_sessions',
168 route_name='admin_settings_sessions', request_method='GET',
192 route_name='admin_settings_sessions', request_method='GET',
169 renderer='rhodecode:templates/admin/settings/settings.mako')
193 renderer='rhodecode:templates/admin/settings/settings.mako')
170
194
171 config.add_route(
195 config.add_route(
172 name='admin_settings_sessions_cleanup',
196 name='admin_settings_sessions_cleanup',
173 pattern='/settings/sessions/cleanup')
197 pattern='/settings/sessions/cleanup')
174 config.add_view(
198 config.add_view(
175 AdminSessionSettingsView,
199 AdminSessionSettingsView,
176 attr='settings_sessions_cleanup',
200 attr='settings_sessions_cleanup',
177 route_name='admin_settings_sessions_cleanup', request_method='POST')
201 route_name='admin_settings_sessions_cleanup', request_method='POST')
178
202
179 config.add_route(
203 config.add_route(
180 name='admin_settings_process_management',
204 name='admin_settings_process_management',
181 pattern='/settings/process_management')
205 pattern='/settings/process_management')
182 config.add_view(
206 config.add_view(
183 AdminProcessManagementView,
207 AdminProcessManagementView,
184 attr='process_management',
208 attr='process_management',
185 route_name='admin_settings_process_management', request_method='GET',
209 route_name='admin_settings_process_management', request_method='GET',
186 renderer='rhodecode:templates/admin/settings/settings.mako')
210 renderer='rhodecode:templates/admin/settings/settings.mako')
187
211
188 config.add_route(
212 config.add_route(
189 name='admin_settings_process_management_data',
213 name='admin_settings_process_management_data',
190 pattern='/settings/process_management/data')
214 pattern='/settings/process_management/data')
191 config.add_view(
215 config.add_view(
192 AdminProcessManagementView,
216 AdminProcessManagementView,
193 attr='process_management_data',
217 attr='process_management_data',
194 route_name='admin_settings_process_management_data', request_method='GET',
218 route_name='admin_settings_process_management_data', request_method='GET',
195 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
219 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
196
220
197 config.add_route(
221 config.add_route(
198 name='admin_settings_process_management_signal',
222 name='admin_settings_process_management_signal',
199 pattern='/settings/process_management/signal')
223 pattern='/settings/process_management/signal')
200 config.add_view(
224 config.add_view(
201 AdminProcessManagementView,
225 AdminProcessManagementView,
202 attr='process_management_signal',
226 attr='process_management_signal',
203 route_name='admin_settings_process_management_signal',
227 route_name='admin_settings_process_management_signal',
204 request_method='POST', renderer='json_ext')
228 request_method='POST', renderer='json_ext')
205
229
206 config.add_route(
230 config.add_route(
207 name='admin_settings_process_management_master_signal',
231 name='admin_settings_process_management_master_signal',
208 pattern='/settings/process_management/master_signal')
232 pattern='/settings/process_management/master_signal')
209 config.add_view(
233 config.add_view(
210 AdminProcessManagementView,
234 AdminProcessManagementView,
211 attr='process_management_master_signal',
235 attr='process_management_master_signal',
212 route_name='admin_settings_process_management_master_signal',
236 route_name='admin_settings_process_management_master_signal',
213 request_method='POST', renderer='json_ext')
237 request_method='POST', renderer='json_ext')
214
238
215 # default settings
239 # default settings
216 config.add_route(
240 config.add_route(
217 name='admin_defaults_repositories',
241 name='admin_defaults_repositories',
218 pattern='/defaults/repositories')
242 pattern='/defaults/repositories')
219 config.add_view(
243 config.add_view(
220 AdminDefaultSettingsView,
244 AdminDefaultSettingsView,
221 attr='defaults_repository_show',
245 attr='defaults_repository_show',
222 route_name='admin_defaults_repositories', request_method='GET',
246 route_name='admin_defaults_repositories', request_method='GET',
223 renderer='rhodecode:templates/admin/defaults/defaults.mako')
247 renderer='rhodecode:templates/admin/defaults/defaults.mako')
224
248
225 config.add_route(
249 config.add_route(
226 name='admin_defaults_repositories_update',
250 name='admin_defaults_repositories_update',
227 pattern='/defaults/repositories/update')
251 pattern='/defaults/repositories/update')
228 config.add_view(
252 config.add_view(
229 AdminDefaultSettingsView,
253 AdminDefaultSettingsView,
230 attr='defaults_repository_update',
254 attr='defaults_repository_update',
231 route_name='admin_defaults_repositories_update', request_method='POST',
255 route_name='admin_defaults_repositories_update', request_method='POST',
232 renderer='rhodecode:templates/admin/defaults/defaults.mako')
256 renderer='rhodecode:templates/admin/defaults/defaults.mako')
233
257
234 # admin settings
258 # admin settings
235
259
236 config.add_route(
260 config.add_route(
237 name='admin_settings',
261 name='admin_settings',
238 pattern='/settings')
262 pattern='/settings')
239 config.add_view(
263 config.add_view(
240 AdminSettingsView,
264 AdminSettingsView,
241 attr='settings_global',
265 attr='settings_global',
242 route_name='admin_settings', request_method='GET',
266 route_name='admin_settings', request_method='GET',
243 renderer='rhodecode:templates/admin/settings/settings.mako')
267 renderer='rhodecode:templates/admin/settings/settings.mako')
244
268
245 config.add_route(
269 config.add_route(
246 name='admin_settings_update',
270 name='admin_settings_update',
247 pattern='/settings/update')
271 pattern='/settings/update')
248 config.add_view(
272 config.add_view(
249 AdminSettingsView,
273 AdminSettingsView,
250 attr='settings_global_update',
274 attr='settings_global_update',
251 route_name='admin_settings_update', request_method='POST',
275 route_name='admin_settings_update', request_method='POST',
252 renderer='rhodecode:templates/admin/settings/settings.mako')
276 renderer='rhodecode:templates/admin/settings/settings.mako')
253
277
254 config.add_route(
278 config.add_route(
255 name='admin_settings_global',
279 name='admin_settings_global',
256 pattern='/settings/global')
280 pattern='/settings/global')
257 config.add_view(
281 config.add_view(
258 AdminSettingsView,
282 AdminSettingsView,
259 attr='settings_global',
283 attr='settings_global',
260 route_name='admin_settings_global', request_method='GET',
284 route_name='admin_settings_global', request_method='GET',
261 renderer='rhodecode:templates/admin/settings/settings.mako')
285 renderer='rhodecode:templates/admin/settings/settings.mako')
262
286
263 config.add_route(
287 config.add_route(
264 name='admin_settings_global_update',
288 name='admin_settings_global_update',
265 pattern='/settings/global/update')
289 pattern='/settings/global/update')
266 config.add_view(
290 config.add_view(
267 AdminSettingsView,
291 AdminSettingsView,
268 attr='settings_global_update',
292 attr='settings_global_update',
269 route_name='admin_settings_global_update', request_method='POST',
293 route_name='admin_settings_global_update', request_method='POST',
270 renderer='rhodecode:templates/admin/settings/settings.mako')
294 renderer='rhodecode:templates/admin/settings/settings.mako')
271
295
272 config.add_route(
296 config.add_route(
273 name='admin_settings_vcs',
297 name='admin_settings_vcs',
274 pattern='/settings/vcs')
298 pattern='/settings/vcs')
275 config.add_view(
299 config.add_view(
276 AdminSettingsView,
300 AdminSettingsView,
277 attr='settings_vcs',
301 attr='settings_vcs',
278 route_name='admin_settings_vcs', request_method='GET',
302 route_name='admin_settings_vcs', request_method='GET',
279 renderer='rhodecode:templates/admin/settings/settings.mako')
303 renderer='rhodecode:templates/admin/settings/settings.mako')
280
304
281 config.add_route(
305 config.add_route(
282 name='admin_settings_vcs_update',
306 name='admin_settings_vcs_update',
283 pattern='/settings/vcs/update')
307 pattern='/settings/vcs/update')
284 config.add_view(
308 config.add_view(
285 AdminSettingsView,
309 AdminSettingsView,
286 attr='settings_vcs_update',
310 attr='settings_vcs_update',
287 route_name='admin_settings_vcs_update', request_method='POST',
311 route_name='admin_settings_vcs_update', request_method='POST',
288 renderer='rhodecode:templates/admin/settings/settings.mako')
312 renderer='rhodecode:templates/admin/settings/settings.mako')
289
313
290 config.add_route(
314 config.add_route(
291 name='admin_settings_vcs_svn_pattern_delete',
315 name='admin_settings_vcs_svn_pattern_delete',
292 pattern='/settings/vcs/svn_pattern_delete')
316 pattern='/settings/vcs/svn_pattern_delete')
293 config.add_view(
317 config.add_view(
294 AdminSettingsView,
318 AdminSettingsView,
295 attr='settings_vcs_delete_svn_pattern',
319 attr='settings_vcs_delete_svn_pattern',
296 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
320 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
297 renderer='json_ext', xhr=True)
321 renderer='json_ext', xhr=True)
298
322
299 config.add_route(
323 config.add_route(
300 name='admin_settings_mapping',
324 name='admin_settings_mapping',
301 pattern='/settings/mapping')
325 pattern='/settings/mapping')
302 config.add_view(
326 config.add_view(
303 AdminSettingsView,
327 AdminSettingsView,
304 attr='settings_mapping',
328 attr='settings_mapping',
305 route_name='admin_settings_mapping', request_method='GET',
329 route_name='admin_settings_mapping', request_method='GET',
306 renderer='rhodecode:templates/admin/settings/settings.mako')
330 renderer='rhodecode:templates/admin/settings/settings.mako')
307
331
308 config.add_route(
332 config.add_route(
309 name='admin_settings_mapping_update',
333 name='admin_settings_mapping_update',
310 pattern='/settings/mapping/update')
334 pattern='/settings/mapping/update')
311 config.add_view(
335 config.add_view(
312 AdminSettingsView,
336 AdminSettingsView,
313 attr='settings_mapping_update',
337 attr='settings_mapping_update',
314 route_name='admin_settings_mapping_update', request_method='POST',
338 route_name='admin_settings_mapping_update', request_method='POST',
315 renderer='rhodecode:templates/admin/settings/settings.mako')
339 renderer='rhodecode:templates/admin/settings/settings.mako')
316
340
317 config.add_route(
341 config.add_route(
318 name='admin_settings_visual',
342 name='admin_settings_visual',
319 pattern='/settings/visual')
343 pattern='/settings/visual')
320 config.add_view(
344 config.add_view(
321 AdminSettingsView,
345 AdminSettingsView,
322 attr='settings_visual',
346 attr='settings_visual',
323 route_name='admin_settings_visual', request_method='GET',
347 route_name='admin_settings_visual', request_method='GET',
324 renderer='rhodecode:templates/admin/settings/settings.mako')
348 renderer='rhodecode:templates/admin/settings/settings.mako')
325
349
326 config.add_route(
350 config.add_route(
327 name='admin_settings_visual_update',
351 name='admin_settings_visual_update',
328 pattern='/settings/visual/update')
352 pattern='/settings/visual/update')
329 config.add_view(
353 config.add_view(
330 AdminSettingsView,
354 AdminSettingsView,
331 attr='settings_visual_update',
355 attr='settings_visual_update',
332 route_name='admin_settings_visual_update', request_method='POST',
356 route_name='admin_settings_visual_update', request_method='POST',
333 renderer='rhodecode:templates/admin/settings/settings.mako')
357 renderer='rhodecode:templates/admin/settings/settings.mako')
334
358
335 config.add_route(
359 config.add_route(
336 name='admin_settings_issuetracker',
360 name='admin_settings_issuetracker',
337 pattern='/settings/issue-tracker')
361 pattern='/settings/issue-tracker')
338 config.add_view(
362 config.add_view(
339 AdminSettingsView,
363 AdminSettingsView,
340 attr='settings_issuetracker',
364 attr='settings_issuetracker',
341 route_name='admin_settings_issuetracker', request_method='GET',
365 route_name='admin_settings_issuetracker', request_method='GET',
342 renderer='rhodecode:templates/admin/settings/settings.mako')
366 renderer='rhodecode:templates/admin/settings/settings.mako')
343
367
344 config.add_route(
368 config.add_route(
345 name='admin_settings_issuetracker_update',
369 name='admin_settings_issuetracker_update',
346 pattern='/settings/issue-tracker/update')
370 pattern='/settings/issue-tracker/update')
347 config.add_view(
371 config.add_view(
348 AdminSettingsView,
372 AdminSettingsView,
349 attr='settings_issuetracker_update',
373 attr='settings_issuetracker_update',
350 route_name='admin_settings_issuetracker_update', request_method='POST',
374 route_name='admin_settings_issuetracker_update', request_method='POST',
351 renderer='rhodecode:templates/admin/settings/settings.mako')
375 renderer='rhodecode:templates/admin/settings/settings.mako')
352
376
353 config.add_route(
377 config.add_route(
354 name='admin_settings_issuetracker_test',
378 name='admin_settings_issuetracker_test',
355 pattern='/settings/issue-tracker/test')
379 pattern='/settings/issue-tracker/test')
356 config.add_view(
380 config.add_view(
357 AdminSettingsView,
381 AdminSettingsView,
358 attr='settings_issuetracker_test',
382 attr='settings_issuetracker_test',
359 route_name='admin_settings_issuetracker_test', request_method='POST',
383 route_name='admin_settings_issuetracker_test', request_method='POST',
360 renderer='string', xhr=True)
384 renderer='string', xhr=True)
361
385
362 config.add_route(
386 config.add_route(
363 name='admin_settings_issuetracker_delete',
387 name='admin_settings_issuetracker_delete',
364 pattern='/settings/issue-tracker/delete')
388 pattern='/settings/issue-tracker/delete')
365 config.add_view(
389 config.add_view(
366 AdminSettingsView,
390 AdminSettingsView,
367 attr='settings_issuetracker_delete',
391 attr='settings_issuetracker_delete',
368 route_name='admin_settings_issuetracker_delete', request_method='POST',
392 route_name='admin_settings_issuetracker_delete', request_method='POST',
369 renderer='json_ext', xhr=True)
393 renderer='json_ext', xhr=True)
370
394
371 config.add_route(
395 config.add_route(
372 name='admin_settings_email',
396 name='admin_settings_email',
373 pattern='/settings/email')
397 pattern='/settings/email')
374 config.add_view(
398 config.add_view(
375 AdminSettingsView,
399 AdminSettingsView,
376 attr='settings_email',
400 attr='settings_email',
377 route_name='admin_settings_email', request_method='GET',
401 route_name='admin_settings_email', request_method='GET',
378 renderer='rhodecode:templates/admin/settings/settings.mako')
402 renderer='rhodecode:templates/admin/settings/settings.mako')
379
403
380 config.add_route(
404 config.add_route(
381 name='admin_settings_email_update',
405 name='admin_settings_email_update',
382 pattern='/settings/email/update')
406 pattern='/settings/email/update')
383 config.add_view(
407 config.add_view(
384 AdminSettingsView,
408 AdminSettingsView,
385 attr='settings_email_update',
409 attr='settings_email_update',
386 route_name='admin_settings_email_update', request_method='POST',
410 route_name='admin_settings_email_update', request_method='POST',
387 renderer='rhodecode:templates/admin/settings/settings.mako')
411 renderer='rhodecode:templates/admin/settings/settings.mako')
388
412
389 config.add_route(
413 config.add_route(
390 name='admin_settings_hooks',
414 name='admin_settings_hooks',
391 pattern='/settings/hooks')
415 pattern='/settings/hooks')
392 config.add_view(
416 config.add_view(
393 AdminSettingsView,
417 AdminSettingsView,
394 attr='settings_hooks',
418 attr='settings_hooks',
395 route_name='admin_settings_hooks', request_method='GET',
419 route_name='admin_settings_hooks', request_method='GET',
396 renderer='rhodecode:templates/admin/settings/settings.mako')
420 renderer='rhodecode:templates/admin/settings/settings.mako')
397
421
398 config.add_route(
422 config.add_route(
399 name='admin_settings_hooks_update',
423 name='admin_settings_hooks_update',
400 pattern='/settings/hooks/update')
424 pattern='/settings/hooks/update')
401 config.add_view(
425 config.add_view(
402 AdminSettingsView,
426 AdminSettingsView,
403 attr='settings_hooks_update',
427 attr='settings_hooks_update',
404 route_name='admin_settings_hooks_update', request_method='POST',
428 route_name='admin_settings_hooks_update', request_method='POST',
405 renderer='rhodecode:templates/admin/settings/settings.mako')
429 renderer='rhodecode:templates/admin/settings/settings.mako')
406
430
407 config.add_route(
431 config.add_route(
408 name='admin_settings_hooks_delete',
432 name='admin_settings_hooks_delete',
409 pattern='/settings/hooks/delete')
433 pattern='/settings/hooks/delete')
410 config.add_view(
434 config.add_view(
411 AdminSettingsView,
435 AdminSettingsView,
412 attr='settings_hooks_update',
436 attr='settings_hooks_update',
413 route_name='admin_settings_hooks_delete', request_method='POST',
437 route_name='admin_settings_hooks_delete', request_method='POST',
414 renderer='rhodecode:templates/admin/settings/settings.mako')
438 renderer='rhodecode:templates/admin/settings/settings.mako')
415
439
416 config.add_route(
440 config.add_route(
417 name='admin_settings_search',
441 name='admin_settings_search',
418 pattern='/settings/search')
442 pattern='/settings/search')
419 config.add_view(
443 config.add_view(
420 AdminSettingsView,
444 AdminSettingsView,
421 attr='settings_search',
445 attr='settings_search',
422 route_name='admin_settings_search', request_method='GET',
446 route_name='admin_settings_search', request_method='GET',
423 renderer='rhodecode:templates/admin/settings/settings.mako')
447 renderer='rhodecode:templates/admin/settings/settings.mako')
424
448
425 config.add_route(
449 config.add_route(
426 name='admin_settings_labs',
450 name='admin_settings_labs',
427 pattern='/settings/labs')
451 pattern='/settings/labs')
428 config.add_view(
452 config.add_view(
429 AdminSettingsView,
453 AdminSettingsView,
430 attr='settings_labs',
454 attr='settings_labs',
431 route_name='admin_settings_labs', request_method='GET',
455 route_name='admin_settings_labs', request_method='GET',
432 renderer='rhodecode:templates/admin/settings/settings.mako')
456 renderer='rhodecode:templates/admin/settings/settings.mako')
433
457
434 config.add_route(
458 config.add_route(
435 name='admin_settings_labs_update',
459 name='admin_settings_labs_update',
436 pattern='/settings/labs/update')
460 pattern='/settings/labs/update')
437 config.add_view(
461 config.add_view(
438 AdminSettingsView,
462 AdminSettingsView,
439 attr='settings_labs_update',
463 attr='settings_labs_update',
440 route_name='admin_settings_labs_update', request_method='POST',
464 route_name='admin_settings_labs_update', request_method='POST',
441 renderer='rhodecode:templates/admin/settings/settings.mako')
465 renderer='rhodecode:templates/admin/settings/settings.mako')
442
466
443 # Automation EE feature
444 config.add_route(
445 'admin_settings_automation',
446 pattern=ADMIN_PREFIX + '/settings/automation')
447 config.add_view(
448 AdminSettingsView,
449 attr='settings_automation',
450 route_name='admin_settings_automation', request_method='GET',
451 renderer='rhodecode:templates/admin/settings/settings.mako')
452
453 # global permissions
467 # global permissions
454
468
455 config.add_route(
469 config.add_route(
456 name='admin_permissions_application',
470 name='admin_permissions_application',
457 pattern='/permissions/application')
471 pattern='/permissions/application')
458 config.add_view(
472 config.add_view(
459 AdminPermissionsView,
473 AdminPermissionsView,
460 attr='permissions_application',
474 attr='permissions_application',
461 route_name='admin_permissions_application', request_method='GET',
475 route_name='admin_permissions_application', request_method='GET',
462 renderer='rhodecode:templates/admin/permissions/permissions.mako')
476 renderer='rhodecode:templates/admin/permissions/permissions.mako')
463
477
464 config.add_route(
478 config.add_route(
465 name='admin_permissions_application_update',
479 name='admin_permissions_application_update',
466 pattern='/permissions/application/update')
480 pattern='/permissions/application/update')
467 config.add_view(
481 config.add_view(
468 AdminPermissionsView,
482 AdminPermissionsView,
469 attr='permissions_application_update',
483 attr='permissions_application_update',
470 route_name='admin_permissions_application_update', request_method='POST',
484 route_name='admin_permissions_application_update', request_method='POST',
471 renderer='rhodecode:templates/admin/permissions/permissions.mako')
485 renderer='rhodecode:templates/admin/permissions/permissions.mako')
472
486
473 config.add_route(
487 config.add_route(
474 name='admin_permissions_global',
488 name='admin_permissions_global',
475 pattern='/permissions/global')
489 pattern='/permissions/global')
476 config.add_view(
490 config.add_view(
477 AdminPermissionsView,
491 AdminPermissionsView,
478 attr='permissions_global',
492 attr='permissions_global',
479 route_name='admin_permissions_global', request_method='GET',
493 route_name='admin_permissions_global', request_method='GET',
480 renderer='rhodecode:templates/admin/permissions/permissions.mako')
494 renderer='rhodecode:templates/admin/permissions/permissions.mako')
481
495
482 config.add_route(
496 config.add_route(
483 name='admin_permissions_global_update',
497 name='admin_permissions_global_update',
484 pattern='/permissions/global/update')
498 pattern='/permissions/global/update')
485 config.add_view(
499 config.add_view(
486 AdminPermissionsView,
500 AdminPermissionsView,
487 attr='permissions_global_update',
501 attr='permissions_global_update',
488 route_name='admin_permissions_global_update', request_method='POST',
502 route_name='admin_permissions_global_update', request_method='POST',
489 renderer='rhodecode:templates/admin/permissions/permissions.mako')
503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
490
504
491 config.add_route(
505 config.add_route(
492 name='admin_permissions_object',
506 name='admin_permissions_object',
493 pattern='/permissions/object')
507 pattern='/permissions/object')
494 config.add_view(
508 config.add_view(
495 AdminPermissionsView,
509 AdminPermissionsView,
496 attr='permissions_objects',
510 attr='permissions_objects',
497 route_name='admin_permissions_object', request_method='GET',
511 route_name='admin_permissions_object', request_method='GET',
498 renderer='rhodecode:templates/admin/permissions/permissions.mako')
512 renderer='rhodecode:templates/admin/permissions/permissions.mako')
499
513
500 config.add_route(
514 config.add_route(
501 name='admin_permissions_object_update',
515 name='admin_permissions_object_update',
502 pattern='/permissions/object/update')
516 pattern='/permissions/object/update')
503 config.add_view(
517 config.add_view(
504 AdminPermissionsView,
518 AdminPermissionsView,
505 attr='permissions_objects_update',
519 attr='permissions_objects_update',
506 route_name='admin_permissions_object_update', request_method='POST',
520 route_name='admin_permissions_object_update', request_method='POST',
507 renderer='rhodecode:templates/admin/permissions/permissions.mako')
521 renderer='rhodecode:templates/admin/permissions/permissions.mako')
508
522
509 # Branch perms EE feature
523 # Branch perms EE feature
510 config.add_route(
524 config.add_route(
511 name='admin_permissions_branch',
525 name='admin_permissions_branch',
512 pattern='/permissions/branch')
526 pattern='/permissions/branch')
513 config.add_view(
527 config.add_view(
514 AdminPermissionsView,
528 AdminPermissionsView,
515 attr='permissions_branch',
529 attr='permissions_branch',
516 route_name='admin_permissions_branch', request_method='GET',
530 route_name='admin_permissions_branch', request_method='GET',
517 renderer='rhodecode:templates/admin/permissions/permissions.mako')
531 renderer='rhodecode:templates/admin/permissions/permissions.mako')
518
532
519 config.add_route(
533 config.add_route(
520 name='admin_permissions_ips',
534 name='admin_permissions_ips',
521 pattern='/permissions/ips')
535 pattern='/permissions/ips')
522 config.add_view(
536 config.add_view(
523 AdminPermissionsView,
537 AdminPermissionsView,
524 attr='permissions_ips',
538 attr='permissions_ips',
525 route_name='admin_permissions_ips', request_method='GET',
539 route_name='admin_permissions_ips', request_method='GET',
526 renderer='rhodecode:templates/admin/permissions/permissions.mako')
540 renderer='rhodecode:templates/admin/permissions/permissions.mako')
527
541
528 config.add_route(
542 config.add_route(
529 name='admin_permissions_overview',
543 name='admin_permissions_overview',
530 pattern='/permissions/overview')
544 pattern='/permissions/overview')
531 config.add_view(
545 config.add_view(
532 AdminPermissionsView,
546 AdminPermissionsView,
533 attr='permissions_overview',
547 attr='permissions_overview',
534 route_name='admin_permissions_overview', request_method='GET',
548 route_name='admin_permissions_overview', request_method='GET',
535 renderer='rhodecode:templates/admin/permissions/permissions.mako')
549 renderer='rhodecode:templates/admin/permissions/permissions.mako')
536
550
537 config.add_route(
551 config.add_route(
538 name='admin_permissions_auth_token_access',
552 name='admin_permissions_auth_token_access',
539 pattern='/permissions/auth_token_access')
553 pattern='/permissions/auth_token_access')
540 config.add_view(
554 config.add_view(
541 AdminPermissionsView,
555 AdminPermissionsView,
542 attr='auth_token_access',
556 attr='auth_token_access',
543 route_name='admin_permissions_auth_token_access', request_method='GET',
557 route_name='admin_permissions_auth_token_access', request_method='GET',
544 renderer='rhodecode:templates/admin/permissions/permissions.mako')
558 renderer='rhodecode:templates/admin/permissions/permissions.mako')
545
559
546 config.add_route(
560 config.add_route(
547 name='admin_permissions_ssh_keys',
561 name='admin_permissions_ssh_keys',
548 pattern='/permissions/ssh_keys')
562 pattern='/permissions/ssh_keys')
549 config.add_view(
563 config.add_view(
550 AdminPermissionsView,
564 AdminPermissionsView,
551 attr='ssh_keys',
565 attr='ssh_keys',
552 route_name='admin_permissions_ssh_keys', request_method='GET',
566 route_name='admin_permissions_ssh_keys', request_method='GET',
553 renderer='rhodecode:templates/admin/permissions/permissions.mako')
567 renderer='rhodecode:templates/admin/permissions/permissions.mako')
554
568
555 config.add_route(
569 config.add_route(
556 name='admin_permissions_ssh_keys_data',
570 name='admin_permissions_ssh_keys_data',
557 pattern='/permissions/ssh_keys/data')
571 pattern='/permissions/ssh_keys/data')
558 config.add_view(
572 config.add_view(
559 AdminPermissionsView,
573 AdminPermissionsView,
560 attr='ssh_keys_data',
574 attr='ssh_keys_data',
561 route_name='admin_permissions_ssh_keys_data', request_method='GET',
575 route_name='admin_permissions_ssh_keys_data', request_method='GET',
562 renderer='json_ext', xhr=True)
576 renderer='json_ext', xhr=True)
563
577
564 config.add_route(
578 config.add_route(
565 name='admin_permissions_ssh_keys_update',
579 name='admin_permissions_ssh_keys_update',
566 pattern='/permissions/ssh_keys/update')
580 pattern='/permissions/ssh_keys/update')
567 config.add_view(
581 config.add_view(
568 AdminPermissionsView,
582 AdminPermissionsView,
569 attr='ssh_keys_update',
583 attr='ssh_keys_update',
570 route_name='admin_permissions_ssh_keys_update', request_method='POST',
584 route_name='admin_permissions_ssh_keys_update', request_method='POST',
571 renderer='rhodecode:templates/admin/permissions/permissions.mako')
585 renderer='rhodecode:templates/admin/permissions/permissions.mako')
572
586
573 # users admin
587 # users admin
574 config.add_route(
588 config.add_route(
575 name='users',
589 name='users',
576 pattern='/users')
590 pattern='/users')
577 config.add_view(
591 config.add_view(
578 AdminUsersView,
592 AdminUsersView,
579 attr='users_list',
593 attr='users_list',
580 route_name='users', request_method='GET',
594 route_name='users', request_method='GET',
581 renderer='rhodecode:templates/admin/users/users.mako')
595 renderer='rhodecode:templates/admin/users/users.mako')
582
596
583 config.add_route(
597 config.add_route(
584 name='users_data',
598 name='users_data',
585 pattern='/users_data')
599 pattern='/users_data')
586 config.add_view(
600 config.add_view(
587 AdminUsersView,
601 AdminUsersView,
588 attr='users_list_data',
602 attr='users_list_data',
589 # renderer defined below
603 # renderer defined below
590 route_name='users_data', request_method='GET',
604 route_name='users_data', request_method='GET',
591 renderer='json_ext', xhr=True)
605 renderer='json_ext', xhr=True)
592
606
593 config.add_route(
607 config.add_route(
594 name='users_create',
608 name='users_create',
595 pattern='/users/create')
609 pattern='/users/create')
596 config.add_view(
610 config.add_view(
597 AdminUsersView,
611 AdminUsersView,
598 attr='users_create',
612 attr='users_create',
599 route_name='users_create', request_method='POST',
613 route_name='users_create', request_method='POST',
600 renderer='rhodecode:templates/admin/users/user_add.mako')
614 renderer='rhodecode:templates/admin/users/user_add.mako')
601
615
602 config.add_route(
616 config.add_route(
603 name='users_new',
617 name='users_new',
604 pattern='/users/new')
618 pattern='/users/new')
605 config.add_view(
619 config.add_view(
606 AdminUsersView,
620 AdminUsersView,
607 attr='users_new',
621 attr='users_new',
608 route_name='users_new', request_method='GET',
622 route_name='users_new', request_method='GET',
609 renderer='rhodecode:templates/admin/users/user_add.mako')
623 renderer='rhodecode:templates/admin/users/user_add.mako')
610
624
611 # user management
625 # user management
612 config.add_route(
626 config.add_route(
613 name='user_edit',
627 name='user_edit',
614 pattern=r'/users/{user_id:\d+}/edit',
628 pattern=r'/users/{user_id:\d+}/edit',
615 user_route=True)
629 user_route=True)
616 config.add_view(
630 config.add_view(
617 UsersView,
631 UsersView,
618 attr='user_edit',
632 attr='user_edit',
619 route_name='user_edit', request_method='GET',
633 route_name='user_edit', request_method='GET',
620 renderer='rhodecode:templates/admin/users/user_edit.mako')
634 renderer='rhodecode:templates/admin/users/user_edit.mako')
621
635
622 config.add_route(
636 config.add_route(
623 name='user_edit_advanced',
637 name='user_edit_advanced',
624 pattern=r'/users/{user_id:\d+}/edit/advanced',
638 pattern=r'/users/{user_id:\d+}/edit/advanced',
625 user_route=True)
639 user_route=True)
626 config.add_view(
640 config.add_view(
627 UsersView,
641 UsersView,
628 attr='user_edit_advanced',
642 attr='user_edit_advanced',
629 route_name='user_edit_advanced', request_method='GET',
643 route_name='user_edit_advanced', request_method='GET',
630 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 renderer='rhodecode:templates/admin/users/user_edit.mako')
631
645
632 config.add_route(
646 config.add_route(
633 name='user_edit_global_perms',
647 name='user_edit_global_perms',
634 pattern=r'/users/{user_id:\d+}/edit/global_permissions',
648 pattern=r'/users/{user_id:\d+}/edit/global_permissions',
635 user_route=True)
649 user_route=True)
636 config.add_view(
650 config.add_view(
637 UsersView,
651 UsersView,
638 attr='user_edit_global_perms',
652 attr='user_edit_global_perms',
639 route_name='user_edit_global_perms', request_method='GET',
653 route_name='user_edit_global_perms', request_method='GET',
640 renderer='rhodecode:templates/admin/users/user_edit.mako')
654 renderer='rhodecode:templates/admin/users/user_edit.mako')
641
655
642 config.add_route(
656 config.add_route(
643 name='user_edit_global_perms_update',
657 name='user_edit_global_perms_update',
644 pattern=r'/users/{user_id:\d+}/edit/global_permissions/update',
658 pattern=r'/users/{user_id:\d+}/edit/global_permissions/update',
645 user_route=True)
659 user_route=True)
646 config.add_view(
660 config.add_view(
647 UsersView,
661 UsersView,
648 attr='user_edit_global_perms_update',
662 attr='user_edit_global_perms_update',
649 route_name='user_edit_global_perms_update', request_method='POST',
663 route_name='user_edit_global_perms_update', request_method='POST',
650 renderer='rhodecode:templates/admin/users/user_edit.mako')
664 renderer='rhodecode:templates/admin/users/user_edit.mako')
651
665
652 config.add_route(
666 config.add_route(
653 name='user_update',
667 name='user_update',
654 pattern=r'/users/{user_id:\d+}/update',
668 pattern=r'/users/{user_id:\d+}/update',
655 user_route=True)
669 user_route=True)
656 config.add_view(
670 config.add_view(
657 UsersView,
671 UsersView,
658 attr='user_update',
672 attr='user_update',
659 route_name='user_update', request_method='POST',
673 route_name='user_update', request_method='POST',
660 renderer='rhodecode:templates/admin/users/user_edit.mako')
674 renderer='rhodecode:templates/admin/users/user_edit.mako')
661
675
662 config.add_route(
676 config.add_route(
663 name='user_delete',
677 name='user_delete',
664 pattern=r'/users/{user_id:\d+}/delete',
678 pattern=r'/users/{user_id:\d+}/delete',
665 user_route=True)
679 user_route=True)
666 config.add_view(
680 config.add_view(
667 UsersView,
681 UsersView,
668 attr='user_delete',
682 attr='user_delete',
669 route_name='user_delete', request_method='POST',
683 route_name='user_delete', request_method='POST',
670 renderer='rhodecode:templates/admin/users/user_edit.mako')
684 renderer='rhodecode:templates/admin/users/user_edit.mako')
671
685
672 config.add_route(
686 config.add_route(
673 name='user_enable_force_password_reset',
687 name='user_enable_force_password_reset',
674 pattern=r'/users/{user_id:\d+}/password_reset_enable',
688 pattern=r'/users/{user_id:\d+}/password_reset_enable',
675 user_route=True)
689 user_route=True)
676 config.add_view(
690 config.add_view(
677 UsersView,
691 UsersView,
678 attr='user_enable_force_password_reset',
692 attr='user_enable_force_password_reset',
679 route_name='user_enable_force_password_reset', request_method='POST',
693 route_name='user_enable_force_password_reset', request_method='POST',
680 renderer='rhodecode:templates/admin/users/user_edit.mako')
694 renderer='rhodecode:templates/admin/users/user_edit.mako')
681
695
682 config.add_route(
696 config.add_route(
683 name='user_disable_force_password_reset',
697 name='user_disable_force_password_reset',
684 pattern=r'/users/{user_id:\d+}/password_reset_disable',
698 pattern=r'/users/{user_id:\d+}/password_reset_disable',
685 user_route=True)
699 user_route=True)
686 config.add_view(
700 config.add_view(
687 UsersView,
701 UsersView,
688 attr='user_disable_force_password_reset',
702 attr='user_disable_force_password_reset',
689 route_name='user_disable_force_password_reset', request_method='POST',
703 route_name='user_disable_force_password_reset', request_method='POST',
690 renderer='rhodecode:templates/admin/users/user_edit.mako')
704 renderer='rhodecode:templates/admin/users/user_edit.mako')
691
705
692 config.add_route(
706 config.add_route(
693 name='user_create_personal_repo_group',
707 name='user_create_personal_repo_group',
694 pattern=r'/users/{user_id:\d+}/create_repo_group',
708 pattern=r'/users/{user_id:\d+}/create_repo_group',
695 user_route=True)
709 user_route=True)
696 config.add_view(
710 config.add_view(
697 UsersView,
711 UsersView,
698 attr='user_create_personal_repo_group',
712 attr='user_create_personal_repo_group',
699 route_name='user_create_personal_repo_group', request_method='POST',
713 route_name='user_create_personal_repo_group', request_method='POST',
700 renderer='rhodecode:templates/admin/users/user_edit.mako')
714 renderer='rhodecode:templates/admin/users/user_edit.mako')
701
715
702 # user notice
716 # user notice
703 config.add_route(
717 config.add_route(
704 name='user_notice_dismiss',
718 name='user_notice_dismiss',
705 pattern=r'/users/{user_id:\d+}/notice_dismiss',
719 pattern=r'/users/{user_id:\d+}/notice_dismiss',
706 user_route=True)
720 user_route=True)
707 config.add_view(
721 config.add_view(
708 UsersView,
722 UsersView,
709 attr='user_notice_dismiss',
723 attr='user_notice_dismiss',
710 route_name='user_notice_dismiss', request_method='POST',
724 route_name='user_notice_dismiss', request_method='POST',
711 renderer='json_ext', xhr=True)
725 renderer='json_ext', xhr=True)
712
726
713 # user auth tokens
727 # user auth tokens
714 config.add_route(
728 config.add_route(
715 name='edit_user_auth_tokens',
729 name='edit_user_auth_tokens',
716 pattern=r'/users/{user_id:\d+}/edit/auth_tokens',
730 pattern=r'/users/{user_id:\d+}/edit/auth_tokens',
717 user_route=True)
731 user_route=True)
718 config.add_view(
732 config.add_view(
719 UsersView,
733 UsersView,
720 attr='auth_tokens',
734 attr='auth_tokens',
721 route_name='edit_user_auth_tokens', request_method='GET',
735 route_name='edit_user_auth_tokens', request_method='GET',
722 renderer='rhodecode:templates/admin/users/user_edit.mako')
736 renderer='rhodecode:templates/admin/users/user_edit.mako')
723
737
724 config.add_route(
738 config.add_route(
725 name='edit_user_auth_tokens_view',
739 name='edit_user_auth_tokens_view',
726 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/view',
740 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/view',
727 user_route=True)
741 user_route=True)
728 config.add_view(
742 config.add_view(
729 UsersView,
743 UsersView,
730 attr='auth_tokens_view',
744 attr='auth_tokens_view',
731 route_name='edit_user_auth_tokens_view', request_method='POST',
745 route_name='edit_user_auth_tokens_view', request_method='POST',
732 renderer='json_ext', xhr=True)
746 renderer='json_ext', xhr=True)
733
747
734 config.add_route(
748 config.add_route(
735 name='edit_user_auth_tokens_add',
749 name='edit_user_auth_tokens_add',
736 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/new',
750 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/new',
737 user_route=True)
751 user_route=True)
738 config.add_view(
752 config.add_view(
739 UsersView,
753 UsersView,
740 attr='auth_tokens_add',
754 attr='auth_tokens_add',
741 route_name='edit_user_auth_tokens_add', request_method='POST')
755 route_name='edit_user_auth_tokens_add', request_method='POST')
742
756
743 config.add_route(
757 config.add_route(
744 name='edit_user_auth_tokens_delete',
758 name='edit_user_auth_tokens_delete',
745 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/delete',
759 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/delete',
746 user_route=True)
760 user_route=True)
747 config.add_view(
761 config.add_view(
748 UsersView,
762 UsersView,
749 attr='auth_tokens_delete',
763 attr='auth_tokens_delete',
750 route_name='edit_user_auth_tokens_delete', request_method='POST')
764 route_name='edit_user_auth_tokens_delete', request_method='POST')
751
765
752 # user ssh keys
766 # user ssh keys
753 config.add_route(
767 config.add_route(
754 name='edit_user_ssh_keys',
768 name='edit_user_ssh_keys',
755 pattern=r'/users/{user_id:\d+}/edit/ssh_keys',
769 pattern=r'/users/{user_id:\d+}/edit/ssh_keys',
756 user_route=True)
770 user_route=True)
757 config.add_view(
771 config.add_view(
758 UsersView,
772 UsersView,
759 attr='ssh_keys',
773 attr='ssh_keys',
760 route_name='edit_user_ssh_keys', request_method='GET',
774 route_name='edit_user_ssh_keys', request_method='GET',
761 renderer='rhodecode:templates/admin/users/user_edit.mako')
775 renderer='rhodecode:templates/admin/users/user_edit.mako')
762
776
763 config.add_route(
777 config.add_route(
764 name='edit_user_ssh_keys_generate_keypair',
778 name='edit_user_ssh_keys_generate_keypair',
765 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/generate',
779 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/generate',
766 user_route=True)
780 user_route=True)
767 config.add_view(
781 config.add_view(
768 UsersView,
782 UsersView,
769 attr='ssh_keys_generate_keypair',
783 attr='ssh_keys_generate_keypair',
770 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
784 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
771 renderer='rhodecode:templates/admin/users/user_edit.mako')
785 renderer='rhodecode:templates/admin/users/user_edit.mako')
772
786
773 config.add_route(
787 config.add_route(
774 name='edit_user_ssh_keys_add',
788 name='edit_user_ssh_keys_add',
775 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/new',
789 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/new',
776 user_route=True)
790 user_route=True)
777 config.add_view(
791 config.add_view(
778 UsersView,
792 UsersView,
779 attr='ssh_keys_add',
793 attr='ssh_keys_add',
780 route_name='edit_user_ssh_keys_add', request_method='POST')
794 route_name='edit_user_ssh_keys_add', request_method='POST')
781
795
782 config.add_route(
796 config.add_route(
783 name='edit_user_ssh_keys_delete',
797 name='edit_user_ssh_keys_delete',
784 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/delete',
798 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/delete',
785 user_route=True)
799 user_route=True)
786 config.add_view(
800 config.add_view(
787 UsersView,
801 UsersView,
788 attr='ssh_keys_delete',
802 attr='ssh_keys_delete',
789 route_name='edit_user_ssh_keys_delete', request_method='POST')
803 route_name='edit_user_ssh_keys_delete', request_method='POST')
790
804
791 # user emails
805 # user emails
792 config.add_route(
806 config.add_route(
793 name='edit_user_emails',
807 name='edit_user_emails',
794 pattern=r'/users/{user_id:\d+}/edit/emails',
808 pattern=r'/users/{user_id:\d+}/edit/emails',
795 user_route=True)
809 user_route=True)
796 config.add_view(
810 config.add_view(
797 UsersView,
811 UsersView,
798 attr='emails',
812 attr='emails',
799 route_name='edit_user_emails', request_method='GET',
813 route_name='edit_user_emails', request_method='GET',
800 renderer='rhodecode:templates/admin/users/user_edit.mako')
814 renderer='rhodecode:templates/admin/users/user_edit.mako')
801
815
802 config.add_route(
816 config.add_route(
803 name='edit_user_emails_add',
817 name='edit_user_emails_add',
804 pattern=r'/users/{user_id:\d+}/edit/emails/new',
818 pattern=r'/users/{user_id:\d+}/edit/emails/new',
805 user_route=True)
819 user_route=True)
806 config.add_view(
820 config.add_view(
807 UsersView,
821 UsersView,
808 attr='emails_add',
822 attr='emails_add',
809 route_name='edit_user_emails_add', request_method='POST')
823 route_name='edit_user_emails_add', request_method='POST')
810
824
811 config.add_route(
825 config.add_route(
812 name='edit_user_emails_delete',
826 name='edit_user_emails_delete',
813 pattern=r'/users/{user_id:\d+}/edit/emails/delete',
827 pattern=r'/users/{user_id:\d+}/edit/emails/delete',
814 user_route=True)
828 user_route=True)
815 config.add_view(
829 config.add_view(
816 UsersView,
830 UsersView,
817 attr='emails_delete',
831 attr='emails_delete',
818 route_name='edit_user_emails_delete', request_method='POST')
832 route_name='edit_user_emails_delete', request_method='POST')
819
833
820 # user IPs
834 # user IPs
821 config.add_route(
835 config.add_route(
822 name='edit_user_ips',
836 name='edit_user_ips',
823 pattern=r'/users/{user_id:\d+}/edit/ips',
837 pattern=r'/users/{user_id:\d+}/edit/ips',
824 user_route=True)
838 user_route=True)
825 config.add_view(
839 config.add_view(
826 UsersView,
840 UsersView,
827 attr='ips',
841 attr='ips',
828 route_name='edit_user_ips', request_method='GET',
842 route_name='edit_user_ips', request_method='GET',
829 renderer='rhodecode:templates/admin/users/user_edit.mako')
843 renderer='rhodecode:templates/admin/users/user_edit.mako')
830
844
831 config.add_route(
845 config.add_route(
832 name='edit_user_ips_add',
846 name='edit_user_ips_add',
833 pattern=r'/users/{user_id:\d+}/edit/ips/new',
847 pattern=r'/users/{user_id:\d+}/edit/ips/new',
834 user_route_with_default=True) # enabled for default user too
848 user_route_with_default=True) # enabled for default user too
835 config.add_view(
849 config.add_view(
836 UsersView,
850 UsersView,
837 attr='ips_add',
851 attr='ips_add',
838 route_name='edit_user_ips_add', request_method='POST')
852 route_name='edit_user_ips_add', request_method='POST')
839
853
840 config.add_route(
854 config.add_route(
841 name='edit_user_ips_delete',
855 name='edit_user_ips_delete',
842 pattern=r'/users/{user_id:\d+}/edit/ips/delete',
856 pattern=r'/users/{user_id:\d+}/edit/ips/delete',
843 user_route_with_default=True) # enabled for default user too
857 user_route_with_default=True) # enabled for default user too
844 config.add_view(
858 config.add_view(
845 UsersView,
859 UsersView,
846 attr='ips_delete',
860 attr='ips_delete',
847 route_name='edit_user_ips_delete', request_method='POST')
861 route_name='edit_user_ips_delete', request_method='POST')
848
862
849 # user perms
863 # user perms
850 config.add_route(
864 config.add_route(
851 name='edit_user_perms_summary',
865 name='edit_user_perms_summary',
852 pattern=r'/users/{user_id:\d+}/edit/permissions_summary',
866 pattern=r'/users/{user_id:\d+}/edit/permissions_summary',
853 user_route=True)
867 user_route=True)
854 config.add_view(
868 config.add_view(
855 UsersView,
869 UsersView,
856 attr='user_perms_summary',
870 attr='user_perms_summary',
857 route_name='edit_user_perms_summary', request_method='GET',
871 route_name='edit_user_perms_summary', request_method='GET',
858 renderer='rhodecode:templates/admin/users/user_edit.mako')
872 renderer='rhodecode:templates/admin/users/user_edit.mako')
859
873
860 config.add_route(
874 config.add_route(
861 name='edit_user_perms_summary_json',
875 name='edit_user_perms_summary_json',
862 pattern=r'/users/{user_id:\d+}/edit/permissions_summary/json',
876 pattern=r'/users/{user_id:\d+}/edit/permissions_summary/json',
863 user_route=True)
877 user_route=True)
864 config.add_view(
878 config.add_view(
865 UsersView,
879 UsersView,
866 attr='user_perms_summary_json',
880 attr='user_perms_summary_json',
867 route_name='edit_user_perms_summary_json', request_method='GET',
881 route_name='edit_user_perms_summary_json', request_method='GET',
868 renderer='json_ext')
882 renderer='json_ext')
869
883
870 # user user groups management
884 # user user groups management
871 config.add_route(
885 config.add_route(
872 name='edit_user_groups_management',
886 name='edit_user_groups_management',
873 pattern=r'/users/{user_id:\d+}/edit/groups_management',
887 pattern=r'/users/{user_id:\d+}/edit/groups_management',
874 user_route=True)
888 user_route=True)
875 config.add_view(
889 config.add_view(
876 UsersView,
890 UsersView,
877 attr='groups_management',
891 attr='groups_management',
878 route_name='edit_user_groups_management', request_method='GET',
892 route_name='edit_user_groups_management', request_method='GET',
879 renderer='rhodecode:templates/admin/users/user_edit.mako')
893 renderer='rhodecode:templates/admin/users/user_edit.mako')
880
894
881 config.add_route(
895 config.add_route(
882 name='edit_user_groups_management_updates',
896 name='edit_user_groups_management_updates',
883 pattern=r'/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
897 pattern=r'/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
884 user_route=True)
898 user_route=True)
885 config.add_view(
899 config.add_view(
886 UsersView,
900 UsersView,
887 attr='groups_management_updates',
901 attr='groups_management_updates',
888 route_name='edit_user_groups_management_updates', request_method='POST')
902 route_name='edit_user_groups_management_updates', request_method='POST')
889
903
890 # user audit logs
904 # user audit logs
891 config.add_route(
905 config.add_route(
892 name='edit_user_audit_logs',
906 name='edit_user_audit_logs',
893 pattern=r'/users/{user_id:\d+}/edit/audit', user_route=True)
907 pattern=r'/users/{user_id:\d+}/edit/audit', user_route=True)
894 config.add_view(
908 config.add_view(
895 UsersView,
909 UsersView,
896 attr='user_audit_logs',
910 attr='user_audit_logs',
897 route_name='edit_user_audit_logs', request_method='GET',
911 route_name='edit_user_audit_logs', request_method='GET',
898 renderer='rhodecode:templates/admin/users/user_edit.mako')
912 renderer='rhodecode:templates/admin/users/user_edit.mako')
899
913
900 config.add_route(
914 config.add_route(
901 name='edit_user_audit_logs_download',
915 name='edit_user_audit_logs_download',
902 pattern=r'/users/{user_id:\d+}/edit/audit/download', user_route=True)
916 pattern=r'/users/{user_id:\d+}/edit/audit/download', user_route=True)
903 config.add_view(
917 config.add_view(
904 UsersView,
918 UsersView,
905 attr='user_audit_logs_download',
919 attr='user_audit_logs_download',
906 route_name='edit_user_audit_logs_download', request_method='GET',
920 route_name='edit_user_audit_logs_download', request_method='GET',
907 renderer='string')
921 renderer='string')
908
922
909 # user caches
923 # user caches
910 config.add_route(
924 config.add_route(
911 name='edit_user_caches',
925 name='edit_user_caches',
912 pattern=r'/users/{user_id:\d+}/edit/caches',
926 pattern=r'/users/{user_id:\d+}/edit/caches',
913 user_route=True)
927 user_route=True)
914 config.add_view(
928 config.add_view(
915 UsersView,
929 UsersView,
916 attr='user_caches',
930 attr='user_caches',
917 route_name='edit_user_caches', request_method='GET',
931 route_name='edit_user_caches', request_method='GET',
918 renderer='rhodecode:templates/admin/users/user_edit.mako')
932 renderer='rhodecode:templates/admin/users/user_edit.mako')
919
933
920 config.add_route(
934 config.add_route(
921 name='edit_user_caches_update',
935 name='edit_user_caches_update',
922 pattern=r'/users/{user_id:\d+}/edit/caches/update',
936 pattern=r'/users/{user_id:\d+}/edit/caches/update',
923 user_route=True)
937 user_route=True)
924 config.add_view(
938 config.add_view(
925 UsersView,
939 UsersView,
926 attr='user_caches_update',
940 attr='user_caches_update',
927 route_name='edit_user_caches_update', request_method='POST')
941 route_name='edit_user_caches_update', request_method='POST')
928
942
929 # user-groups admin
943 # user-groups admin
930 config.add_route(
944 config.add_route(
931 name='user_groups',
945 name='user_groups',
932 pattern='/user_groups')
946 pattern='/user_groups')
933 config.add_view(
947 config.add_view(
934 AdminUserGroupsView,
948 AdminUserGroupsView,
935 attr='user_groups_list',
949 attr='user_groups_list',
936 route_name='user_groups', request_method='GET',
950 route_name='user_groups', request_method='GET',
937 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
951 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
938
952
939 config.add_route(
953 config.add_route(
940 name='user_groups_data',
954 name='user_groups_data',
941 pattern='/user_groups_data')
955 pattern='/user_groups_data')
942 config.add_view(
956 config.add_view(
943 AdminUserGroupsView,
957 AdminUserGroupsView,
944 attr='user_groups_list_data',
958 attr='user_groups_list_data',
945 route_name='user_groups_data', request_method='GET',
959 route_name='user_groups_data', request_method='GET',
946 renderer='json_ext', xhr=True)
960 renderer='json_ext', xhr=True)
947
961
948 config.add_route(
962 config.add_route(
949 name='user_groups_new',
963 name='user_groups_new',
950 pattern='/user_groups/new')
964 pattern='/user_groups/new')
951 config.add_view(
965 config.add_view(
952 AdminUserGroupsView,
966 AdminUserGroupsView,
953 attr='user_groups_new',
967 attr='user_groups_new',
954 route_name='user_groups_new', request_method='GET',
968 route_name='user_groups_new', request_method='GET',
955 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
969 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
956
970
957 config.add_route(
971 config.add_route(
958 name='user_groups_create',
972 name='user_groups_create',
959 pattern='/user_groups/create')
973 pattern='/user_groups/create')
960 config.add_view(
974 config.add_view(
961 AdminUserGroupsView,
975 AdminUserGroupsView,
962 attr='user_groups_create',
976 attr='user_groups_create',
963 route_name='user_groups_create', request_method='POST',
977 route_name='user_groups_create', request_method='POST',
964 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
978 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
965
979
966 # repos admin
980 # repos admin
967 config.add_route(
981 config.add_route(
968 name='repos',
982 name='repos',
969 pattern='/repos')
983 pattern='/repos')
970 config.add_view(
984 config.add_view(
971 AdminReposView,
985 AdminReposView,
972 attr='repository_list',
986 attr='repository_list',
973 route_name='repos', request_method='GET',
987 route_name='repos', request_method='GET',
974 renderer='rhodecode:templates/admin/repos/repos.mako')
988 renderer='rhodecode:templates/admin/repos/repos.mako')
975
989
976 config.add_route(
990 config.add_route(
977 name='repos_data',
991 name='repos_data',
978 pattern='/repos_data')
992 pattern='/repos_data')
979 config.add_view(
993 config.add_view(
980 AdminReposView,
994 AdminReposView,
981 attr='repository_list_data',
995 attr='repository_list_data',
982 route_name='repos_data', request_method='GET',
996 route_name='repos_data', request_method='GET',
983 renderer='json_ext', xhr=True)
997 renderer='json_ext', xhr=True)
984
998
985 config.add_route(
999 config.add_route(
986 name='repo_new',
1000 name='repo_new',
987 pattern='/repos/new')
1001 pattern='/repos/new')
988 config.add_view(
1002 config.add_view(
989 AdminReposView,
1003 AdminReposView,
990 attr='repository_new',
1004 attr='repository_new',
991 route_name='repo_new', request_method='GET',
1005 route_name='repo_new', request_method='GET',
992 renderer='rhodecode:templates/admin/repos/repo_add.mako')
1006 renderer='rhodecode:templates/admin/repos/repo_add.mako')
993
1007
994 config.add_route(
1008 config.add_route(
995 name='repo_create',
1009 name='repo_create',
996 pattern='/repos/create')
1010 pattern='/repos/create')
997 config.add_view(
1011 config.add_view(
998 AdminReposView,
1012 AdminReposView,
999 attr='repository_create',
1013 attr='repository_create',
1000 route_name='repo_create', request_method='POST',
1014 route_name='repo_create', request_method='POST',
1001 renderer='rhodecode:templates/admin/repos/repos.mako')
1015 renderer='rhodecode:templates/admin/repos/repos.mako')
1002
1016
1003 # repo groups admin
1017 # repo groups admin
1004 config.add_route(
1018 config.add_route(
1005 name='repo_groups',
1019 name='repo_groups',
1006 pattern='/repo_groups')
1020 pattern='/repo_groups')
1007 config.add_view(
1021 config.add_view(
1008 AdminRepoGroupsView,
1022 AdminRepoGroupsView,
1009 attr='repo_group_list',
1023 attr='repo_group_list',
1010 route_name='repo_groups', request_method='GET',
1024 route_name='repo_groups', request_method='GET',
1011 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
1025 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
1012
1026
1013 config.add_route(
1027 config.add_route(
1014 name='repo_groups_data',
1028 name='repo_groups_data',
1015 pattern='/repo_groups_data')
1029 pattern='/repo_groups_data')
1016 config.add_view(
1030 config.add_view(
1017 AdminRepoGroupsView,
1031 AdminRepoGroupsView,
1018 attr='repo_group_list_data',
1032 attr='repo_group_list_data',
1019 route_name='repo_groups_data', request_method='GET',
1033 route_name='repo_groups_data', request_method='GET',
1020 renderer='json_ext', xhr=True)
1034 renderer='json_ext', xhr=True)
1021
1035
1022 config.add_route(
1036 config.add_route(
1023 name='repo_group_new',
1037 name='repo_group_new',
1024 pattern='/repo_group/new')
1038 pattern='/repo_group/new')
1025 config.add_view(
1039 config.add_view(
1026 AdminRepoGroupsView,
1040 AdminRepoGroupsView,
1027 attr='repo_group_new',
1041 attr='repo_group_new',
1028 route_name='repo_group_new', request_method='GET',
1042 route_name='repo_group_new', request_method='GET',
1029 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1043 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1030
1044
1031 config.add_route(
1045 config.add_route(
1032 name='repo_group_create',
1046 name='repo_group_create',
1033 pattern='/repo_group/create')
1047 pattern='/repo_group/create')
1034 config.add_view(
1048 config.add_view(
1035 AdminRepoGroupsView,
1049 AdminRepoGroupsView,
1036 attr='repo_group_create',
1050 attr='repo_group_create',
1037 route_name='repo_group_create', request_method='POST',
1051 route_name='repo_group_create', request_method='POST',
1038 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1052 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1039
1053
1040
1054
1041 def includeme(config):
1055 def includeme(config):
1042 from rhodecode.apps._base.navigation import includeme as nav_includeme
1043 from rhodecode.apps.admin.views.main_views import AdminMainView
1044
1045 # Create admin navigation registry and add it to the pyramid registry.
1056 # Create admin navigation registry and add it to the pyramid registry.
1046 nav_includeme(config)
1057 nav_includeme(config)
1047
1058
1048 # main admin routes
1059 # main admin routes
1049 config.add_route(
1060 config.add_route(
1050 name='admin_home', pattern=ADMIN_PREFIX)
1061 name='admin_home', pattern=ADMIN_PREFIX)
1051 config.add_view(
1062 config.add_view(
1052 AdminMainView,
1063 AdminMainView,
1053 attr='admin_main',
1064 attr='admin_main',
1054 route_name='admin_home', request_method='GET',
1065 route_name='admin_home', request_method='GET',
1055 renderer='rhodecode:templates/admin/main.mako')
1066 renderer='rhodecode:templates/admin/main.mako')
1056
1067
1057 # pr global redirect
1068 # pr global redirect
1058 config.add_route(
1069 config.add_route(
1059 name='pull_requests_global_0', # backward compat
1070 name='pull_requests_global_0', # backward compat
1060 pattern=ADMIN_PREFIX + r'/pull_requests/{pull_request_id:\d+}')
1071 pattern=ADMIN_PREFIX + r'/pull_requests/{pull_request_id:\d+}')
1061 config.add_view(
1072 config.add_view(
1062 AdminMainView,
1073 AdminMainView,
1063 attr='pull_requests',
1074 attr='pull_requests',
1064 route_name='pull_requests_global_0', request_method='GET')
1075 route_name='pull_requests_global_0', request_method='GET')
1065
1076
1066 config.add_route(
1077 config.add_route(
1067 name='pull_requests_global_1', # backward compat
1078 name='pull_requests_global_1', # backward compat
1068 pattern=ADMIN_PREFIX + r'/pull-requests/{pull_request_id:\d+}')
1079 pattern=ADMIN_PREFIX + r'/pull-requests/{pull_request_id:\d+}')
1069 config.add_view(
1080 config.add_view(
1070 AdminMainView,
1081 AdminMainView,
1071 attr='pull_requests',
1082 attr='pull_requests',
1072 route_name='pull_requests_global_1', request_method='GET')
1083 route_name='pull_requests_global_1', request_method='GET')
1073
1084
1074 config.add_route(
1085 config.add_route(
1075 name='pull_requests_global',
1086 name='pull_requests_global',
1076 pattern=ADMIN_PREFIX + r'/pull-request/{pull_request_id:\d+}')
1087 pattern=ADMIN_PREFIX + r'/pull-request/{pull_request_id:\d+}')
1077 config.add_view(
1088 config.add_view(
1078 AdminMainView,
1089 AdminMainView,
1079 attr='pull_requests',
1090 attr='pull_requests',
1080 route_name='pull_requests_global', request_method='GET')
1091 route_name='pull_requests_global', request_method='GET')
1081
1092
1082 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
1093 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
@@ -1,721 +1,714 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 import logging
20 import logging
21 import collections
21 import collections
22
22
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 import rhodecode
27 import rhodecode
28
28
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.apps._base.navigation import navigation_list
34 from rhodecode.apps._base.navigation import navigation_list
35 from rhodecode.apps.svn_support.config_keys import generate_config
35 from rhodecode.apps.svn_support.config_keys import generate_config
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 from rhodecode.lib.celerylib import tasks, run_task
39 from rhodecode.lib.celerylib import tasks, run_task
40 from rhodecode.lib.str_utils import safe_str
40 from rhodecode.lib.str_utils import safe_str
41 from rhodecode.lib.utils import repo2db_mapper
41 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
43 from rhodecode.lib.index import searcher_from_config
43 from rhodecode.lib.index import searcher_from_config
44
44
45 from rhodecode.model.db import RhodeCodeUi, Repository
45 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.forms import (ApplicationSettingsForm,
46 from rhodecode.model.forms import (ApplicationSettingsForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 LabsSettingsForm, IssueTrackerPatternsForm)
48 LabsSettingsForm, IssueTrackerPatternsForm)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70 return c
70 return c
71
71
72 @classmethod
72 @classmethod
73 def _get_ui_settings(cls):
73 def _get_ui_settings(cls):
74 ret = RhodeCodeUi.query().all()
74 ret = RhodeCodeUi.query().all()
75
75
76 if not ret:
76 if not ret:
77 raise Exception('Could not get application ui settings !')
77 raise Exception('Could not get application ui settings !')
78 settings = {}
78 settings = {}
79 for each in ret:
79 for each in ret:
80 k = each.ui_key
80 k = each.ui_key
81 v = each.ui_value
81 v = each.ui_value
82 if k == '/':
82 if k == '/':
83 k = 'root_path'
83 k = 'root_path'
84
84
85 if k in ['push_ssl', 'publish', 'enabled']:
85 if k in ['push_ssl', 'publish', 'enabled']:
86 v = str2bool(v)
86 v = str2bool(v)
87
87
88 if k.find('.') != -1:
88 if k.find('.') != -1:
89 k = k.replace('.', '_')
89 k = k.replace('.', '_')
90
90
91 if each.ui_section in ['hooks', 'extensions']:
91 if each.ui_section in ['hooks', 'extensions']:
92 v = each.ui_active
92 v = each.ui_active
93
93
94 settings[each.ui_section + '_' + k] = v
94 settings[each.ui_section + '_' + k] = v
95 return settings
95 return settings
96
96
97 @classmethod
97 @classmethod
98 def _form_defaults(cls):
98 def _form_defaults(cls):
99 defaults = SettingsModel().get_all_settings()
99 defaults = SettingsModel().get_all_settings()
100 defaults.update(cls._get_ui_settings())
100 defaults.update(cls._get_ui_settings())
101
101
102 defaults.update({
102 defaults.update({
103 'new_svn_branch': '',
103 'new_svn_branch': '',
104 'new_svn_tag': '',
104 'new_svn_tag': '',
105 })
105 })
106 return defaults
106 return defaults
107
107
108 @LoginRequired()
108 @LoginRequired()
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 def settings_vcs(self):
110 def settings_vcs(self):
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'vcs'
112 c.active = 'vcs'
113 model = VcsSettingsModel()
113 model = VcsSettingsModel()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
116
116
117 settings = self.request.registry.settings
117 settings = self.request.registry.settings
118 c.svn_proxy_generate_config = settings[generate_config]
118 c.svn_proxy_generate_config = settings[generate_config]
119
119
120 defaults = self._form_defaults()
120 defaults = self._form_defaults()
121
121
122 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
122 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
123
123
124 data = render('rhodecode:templates/admin/settings/settings.mako',
124 data = render('rhodecode:templates/admin/settings/settings.mako',
125 self._get_template_context(c), self.request)
125 self._get_template_context(c), self.request)
126 html = formencode.htmlfill.render(
126 html = formencode.htmlfill.render(
127 data,
127 data,
128 defaults=defaults,
128 defaults=defaults,
129 encoding="UTF-8",
129 encoding="UTF-8",
130 force_defaults=False
130 force_defaults=False
131 )
131 )
132 return Response(html)
132 return Response(html)
133
133
134 @LoginRequired()
134 @LoginRequired()
135 @HasPermissionAllDecorator('hg.admin')
135 @HasPermissionAllDecorator('hg.admin')
136 @CSRFRequired()
136 @CSRFRequired()
137 def settings_vcs_update(self):
137 def settings_vcs_update(self):
138 _ = self.request.translate
138 _ = self.request.translate
139 c = self.load_default_context()
139 c = self.load_default_context()
140 c.active = 'vcs'
140 c.active = 'vcs'
141
141
142 model = VcsSettingsModel()
142 model = VcsSettingsModel()
143 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
143 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
144 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
144 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
145
145
146 settings = self.request.registry.settings
146 settings = self.request.registry.settings
147 c.svn_proxy_generate_config = settings[generate_config]
147 c.svn_proxy_generate_config = settings[generate_config]
148
148
149 application_form = ApplicationUiSettingsForm(self.request.translate)()
149 application_form = ApplicationUiSettingsForm(self.request.translate)()
150
150
151 try:
151 try:
152 form_result = application_form.to_python(dict(self.request.POST))
152 form_result = application_form.to_python(dict(self.request.POST))
153 except formencode.Invalid as errors:
153 except formencode.Invalid as errors:
154 h.flash(
154 h.flash(
155 _("Some form inputs contain invalid data."),
155 _("Some form inputs contain invalid data."),
156 category='error')
156 category='error')
157 data = render('rhodecode:templates/admin/settings/settings.mako',
157 data = render('rhodecode:templates/admin/settings/settings.mako',
158 self._get_template_context(c), self.request)
158 self._get_template_context(c), self.request)
159 html = formencode.htmlfill.render(
159 html = formencode.htmlfill.render(
160 data,
160 data,
161 defaults=errors.value,
161 defaults=errors.value,
162 errors=errors.unpack_errors() or {},
162 errors=errors.unpack_errors() or {},
163 prefix_error=False,
163 prefix_error=False,
164 encoding="UTF-8",
164 encoding="UTF-8",
165 force_defaults=False
165 force_defaults=False
166 )
166 )
167 return Response(html)
167 return Response(html)
168
168
169 try:
169 try:
170 if c.visual.allow_repo_location_change:
170 if c.visual.allow_repo_location_change:
171 model.update_global_path_setting(form_result['paths_root_path'])
171 model.update_global_path_setting(form_result['paths_root_path'])
172
172
173 model.update_global_ssl_setting(form_result['web_push_ssl'])
173 model.update_global_ssl_setting(form_result['web_push_ssl'])
174 model.update_global_hook_settings(form_result)
174 model.update_global_hook_settings(form_result)
175
175
176 model.create_or_update_global_svn_settings(form_result)
176 model.create_or_update_global_svn_settings(form_result)
177 model.create_or_update_global_hg_settings(form_result)
177 model.create_or_update_global_hg_settings(form_result)
178 model.create_or_update_global_git_settings(form_result)
178 model.create_or_update_global_git_settings(form_result)
179 model.create_or_update_global_pr_settings(form_result)
179 model.create_or_update_global_pr_settings(form_result)
180 except Exception:
180 except Exception:
181 log.exception("Exception while updating settings")
181 log.exception("Exception while updating settings")
182 h.flash(_('Error occurred during updating '
182 h.flash(_('Error occurred during updating '
183 'application settings'), category='error')
183 'application settings'), category='error')
184 else:
184 else:
185 Session().commit()
185 Session().commit()
186 h.flash(_('Updated VCS settings'), category='success')
186 h.flash(_('Updated VCS settings'), category='success')
187 raise HTTPFound(h.route_path('admin_settings_vcs'))
187 raise HTTPFound(h.route_path('admin_settings_vcs'))
188
188
189 data = render('rhodecode:templates/admin/settings/settings.mako',
189 data = render('rhodecode:templates/admin/settings/settings.mako',
190 self._get_template_context(c), self.request)
190 self._get_template_context(c), self.request)
191 html = formencode.htmlfill.render(
191 html = formencode.htmlfill.render(
192 data,
192 data,
193 defaults=self._form_defaults(),
193 defaults=self._form_defaults(),
194 encoding="UTF-8",
194 encoding="UTF-8",
195 force_defaults=False
195 force_defaults=False
196 )
196 )
197 return Response(html)
197 return Response(html)
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @HasPermissionAllDecorator('hg.admin')
200 @HasPermissionAllDecorator('hg.admin')
201 @CSRFRequired()
201 @CSRFRequired()
202 def settings_vcs_delete_svn_pattern(self):
202 def settings_vcs_delete_svn_pattern(self):
203 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
203 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
204 model = VcsSettingsModel()
204 model = VcsSettingsModel()
205 try:
205 try:
206 model.delete_global_svn_pattern(delete_pattern_id)
206 model.delete_global_svn_pattern(delete_pattern_id)
207 except SettingNotFound:
207 except SettingNotFound:
208 log.exception(
208 log.exception(
209 'Failed to delete svn_pattern with id %s', delete_pattern_id)
209 'Failed to delete svn_pattern with id %s', delete_pattern_id)
210 raise HTTPNotFound()
210 raise HTTPNotFound()
211
211
212 Session().commit()
212 Session().commit()
213 return True
213 return True
214
214
215 @LoginRequired()
215 @LoginRequired()
216 @HasPermissionAllDecorator('hg.admin')
216 @HasPermissionAllDecorator('hg.admin')
217 def settings_mapping(self):
217 def settings_mapping(self):
218 c = self.load_default_context()
218 c = self.load_default_context()
219 c.active = 'mapping'
219 c.active = 'mapping'
220
220
221 data = render('rhodecode:templates/admin/settings/settings.mako',
221 data = render('rhodecode:templates/admin/settings/settings.mako',
222 self._get_template_context(c), self.request)
222 self._get_template_context(c), self.request)
223 html = formencode.htmlfill.render(
223 html = formencode.htmlfill.render(
224 data,
224 data,
225 defaults=self._form_defaults(),
225 defaults=self._form_defaults(),
226 encoding="UTF-8",
226 encoding="UTF-8",
227 force_defaults=False
227 force_defaults=False
228 )
228 )
229 return Response(html)
229 return Response(html)
230
230
231 @LoginRequired()
231 @LoginRequired()
232 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
233 @CSRFRequired()
233 @CSRFRequired()
234 def settings_mapping_update(self):
234 def settings_mapping_update(self):
235 _ = self.request.translate
235 _ = self.request.translate
236 c = self.load_default_context()
236 c = self.load_default_context()
237 c.active = 'mapping'
237 c.active = 'mapping'
238 rm_obsolete = self.request.POST.get('destroy', False)
238 rm_obsolete = self.request.POST.get('destroy', False)
239 invalidate_cache = self.request.POST.get('invalidate', False)
239 invalidate_cache = self.request.POST.get('invalidate', False)
240 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
240 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
241
241
242 if invalidate_cache:
242 if invalidate_cache:
243 log.debug('invalidating all repositories cache')
243 log.debug('invalidating all repositories cache')
244 for repo in Repository.get_all():
244 for repo in Repository.get_all():
245 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
245 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
246
246
247 filesystem_repos = ScmModel().repo_scan()
247 filesystem_repos = ScmModel().repo_scan()
248 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
248 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
249 PermissionModel().trigger_permission_flush()
249 PermissionModel().trigger_permission_flush()
250
250
251 def _repr(l):
251 def _repr(rm_repo):
252 return ', '.join(map(safe_str, l)) or '-'
252 return ', '.join(map(safe_str, rm_repo)) or '-'
253
253 h.flash(_('Repositories successfully '
254 h.flash(_('Repositories successfully '
254 'rescanned added: %s ; removed: %s') %
255 'rescanned added: %s ; removed: %s') %
255 (_repr(added), _repr(removed)),
256 (_repr(added), _repr(removed)),
256 category='success')
257 category='success')
257 raise HTTPFound(h.route_path('admin_settings_mapping'))
258 raise HTTPFound(h.route_path('admin_settings_mapping'))
258
259
259 @LoginRequired()
260 @LoginRequired()
260 @HasPermissionAllDecorator('hg.admin')
261 @HasPermissionAllDecorator('hg.admin')
261 def settings_global(self):
262 def settings_global(self):
262 c = self.load_default_context()
263 c = self.load_default_context()
263 c.active = 'global'
264 c.active = 'global'
264 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 .get_personal_group_name_pattern()
266 .get_personal_group_name_pattern()
266
267
267 data = render('rhodecode:templates/admin/settings/settings.mako',
268 data = render('rhodecode:templates/admin/settings/settings.mako',
268 self._get_template_context(c), self.request)
269 self._get_template_context(c), self.request)
269 html = formencode.htmlfill.render(
270 html = formencode.htmlfill.render(
270 data,
271 data,
271 defaults=self._form_defaults(),
272 defaults=self._form_defaults(),
272 encoding="UTF-8",
273 encoding="UTF-8",
273 force_defaults=False
274 force_defaults=False
274 )
275 )
275 return Response(html)
276 return Response(html)
276
277
277 @LoginRequired()
278 @LoginRequired()
278 @HasPermissionAllDecorator('hg.admin')
279 @HasPermissionAllDecorator('hg.admin')
279 @CSRFRequired()
280 @CSRFRequired()
280 def settings_global_update(self):
281 def settings_global_update(self):
281 _ = self.request.translate
282 _ = self.request.translate
282 c = self.load_default_context()
283 c = self.load_default_context()
283 c.active = 'global'
284 c.active = 'global'
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 .get_personal_group_name_pattern()
286 .get_personal_group_name_pattern()
286 application_form = ApplicationSettingsForm(self.request.translate)()
287 application_form = ApplicationSettingsForm(self.request.translate)()
287 try:
288 try:
288 form_result = application_form.to_python(dict(self.request.POST))
289 form_result = application_form.to_python(dict(self.request.POST))
289 except formencode.Invalid as errors:
290 except formencode.Invalid as errors:
290 h.flash(
291 h.flash(
291 _("Some form inputs contain invalid data."),
292 _("Some form inputs contain invalid data."),
292 category='error')
293 category='error')
293 data = render('rhodecode:templates/admin/settings/settings.mako',
294 data = render('rhodecode:templates/admin/settings/settings.mako',
294 self._get_template_context(c), self.request)
295 self._get_template_context(c), self.request)
295 html = formencode.htmlfill.render(
296 html = formencode.htmlfill.render(
296 data,
297 data,
297 defaults=errors.value,
298 defaults=errors.value,
298 errors=errors.unpack_errors() or {},
299 errors=errors.unpack_errors() or {},
299 prefix_error=False,
300 prefix_error=False,
300 encoding="UTF-8",
301 encoding="UTF-8",
301 force_defaults=False
302 force_defaults=False
302 )
303 )
303 return Response(html)
304 return Response(html)
304
305
305 settings = [
306 settings = [
306 ('title', 'rhodecode_title', 'unicode'),
307 ('title', 'rhodecode_title', 'unicode'),
307 ('realm', 'rhodecode_realm', 'unicode'),
308 ('realm', 'rhodecode_realm', 'unicode'),
308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 ('post_code', 'rhodecode_post_code', 'unicode'),
310 ('post_code', 'rhodecode_post_code', 'unicode'),
310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 ]
315 ]
315
316
316 try:
317 try:
317 for setting, form_key, type_ in settings:
318 for setting, form_key, type_ in settings:
318 sett = SettingsModel().create_or_update_setting(
319 sett = SettingsModel().create_or_update_setting(
319 setting, form_result[form_key], type_)
320 setting, form_result[form_key], type_)
320 Session().add(sett)
321 Session().add(sett)
321
322
322 Session().commit()
323 Session().commit()
323 SettingsModel().invalidate_settings_cache()
324 SettingsModel().invalidate_settings_cache()
324 h.flash(_('Updated application settings'), category='success')
325 h.flash(_('Updated application settings'), category='success')
325 except Exception:
326 except Exception:
326 log.exception("Exception while updating application settings")
327 log.exception("Exception while updating application settings")
327 h.flash(
328 h.flash(
328 _('Error occurred during updating application settings'),
329 _('Error occurred during updating application settings'),
329 category='error')
330 category='error')
330
331
331 raise HTTPFound(h.route_path('admin_settings_global'))
332 raise HTTPFound(h.route_path('admin_settings_global'))
332
333
333 @LoginRequired()
334 @LoginRequired()
334 @HasPermissionAllDecorator('hg.admin')
335 @HasPermissionAllDecorator('hg.admin')
335 def settings_visual(self):
336 def settings_visual(self):
336 c = self.load_default_context()
337 c = self.load_default_context()
337 c.active = 'visual'
338 c.active = 'visual'
338
339
339 data = render('rhodecode:templates/admin/settings/settings.mako',
340 data = render('rhodecode:templates/admin/settings/settings.mako',
340 self._get_template_context(c), self.request)
341 self._get_template_context(c), self.request)
341 html = formencode.htmlfill.render(
342 html = formencode.htmlfill.render(
342 data,
343 data,
343 defaults=self._form_defaults(),
344 defaults=self._form_defaults(),
344 encoding="UTF-8",
345 encoding="UTF-8",
345 force_defaults=False
346 force_defaults=False
346 )
347 )
347 return Response(html)
348 return Response(html)
348
349
349 @LoginRequired()
350 @LoginRequired()
350 @HasPermissionAllDecorator('hg.admin')
351 @HasPermissionAllDecorator('hg.admin')
351 @CSRFRequired()
352 @CSRFRequired()
352 def settings_visual_update(self):
353 def settings_visual_update(self):
353 _ = self.request.translate
354 _ = self.request.translate
354 c = self.load_default_context()
355 c = self.load_default_context()
355 c.active = 'visual'
356 c.active = 'visual'
356 application_form = ApplicationVisualisationForm(self.request.translate)()
357 application_form = ApplicationVisualisationForm(self.request.translate)()
357 try:
358 try:
358 form_result = application_form.to_python(dict(self.request.POST))
359 form_result = application_form.to_python(dict(self.request.POST))
359 except formencode.Invalid as errors:
360 except formencode.Invalid as errors:
360 h.flash(
361 h.flash(
361 _("Some form inputs contain invalid data."),
362 _("Some form inputs contain invalid data."),
362 category='error')
363 category='error')
363 data = render('rhodecode:templates/admin/settings/settings.mako',
364 data = render('rhodecode:templates/admin/settings/settings.mako',
364 self._get_template_context(c), self.request)
365 self._get_template_context(c), self.request)
365 html = formencode.htmlfill.render(
366 html = formencode.htmlfill.render(
366 data,
367 data,
367 defaults=errors.value,
368 defaults=errors.value,
368 errors=errors.unpack_errors() or {},
369 errors=errors.unpack_errors() or {},
369 prefix_error=False,
370 prefix_error=False,
370 encoding="UTF-8",
371 encoding="UTF-8",
371 force_defaults=False
372 force_defaults=False
372 )
373 )
373 return Response(html)
374 return Response(html)
374
375
375 try:
376 try:
376 settings = [
377 settings = [
377 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
378 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
378 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
379 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
379 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
380 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
380 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
381 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
381 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
382 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
382 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
383 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
383 ('show_version', 'rhodecode_show_version', 'bool'),
384 ('show_version', 'rhodecode_show_version', 'bool'),
384 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
385 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
385 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
386 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
386 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
387 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
387 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
388 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
388 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
389 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
389 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
390 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
390 ('support_url', 'rhodecode_support_url', 'unicode'),
391 ('support_url', 'rhodecode_support_url', 'unicode'),
391 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
392 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
392 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
393 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
393 ]
394 ]
394 for setting, form_key, type_ in settings:
395 for setting, form_key, type_ in settings:
395 sett = SettingsModel().create_or_update_setting(
396 sett = SettingsModel().create_or_update_setting(
396 setting, form_result[form_key], type_)
397 setting, form_result[form_key], type_)
397 Session().add(sett)
398 Session().add(sett)
398
399
399 Session().commit()
400 Session().commit()
400 SettingsModel().invalidate_settings_cache()
401 SettingsModel().invalidate_settings_cache()
401 h.flash(_('Updated visualisation settings'), category='success')
402 h.flash(_('Updated visualisation settings'), category='success')
402 except Exception:
403 except Exception:
403 log.exception("Exception updating visualization settings")
404 log.exception("Exception updating visualization settings")
404 h.flash(_('Error occurred during updating '
405 h.flash(_('Error occurred during updating '
405 'visualisation settings'),
406 'visualisation settings'),
406 category='error')
407 category='error')
407
408
408 raise HTTPFound(h.route_path('admin_settings_visual'))
409 raise HTTPFound(h.route_path('admin_settings_visual'))
409
410
410 @LoginRequired()
411 @LoginRequired()
411 @HasPermissionAllDecorator('hg.admin')
412 @HasPermissionAllDecorator('hg.admin')
412 def settings_issuetracker(self):
413 def settings_issuetracker(self):
413 c = self.load_default_context()
414 c = self.load_default_context()
414 c.active = 'issuetracker'
415 c.active = 'issuetracker'
415 defaults = c.rc_config
416 defaults = c.rc_config
416
417
417 entry_key = 'rhodecode_issuetracker_pat_'
418 entry_key = 'rhodecode_issuetracker_pat_'
418
419
419 c.issuetracker_entries = {}
420 c.issuetracker_entries = {}
420 for k, v in defaults.items():
421 for k, v in defaults.items():
421 if k.startswith(entry_key):
422 if k.startswith(entry_key):
422 uid = k[len(entry_key):]
423 uid = k[len(entry_key):]
423 c.issuetracker_entries[uid] = None
424 c.issuetracker_entries[uid] = None
424
425
425 for uid in c.issuetracker_entries:
426 for uid in c.issuetracker_entries:
426 c.issuetracker_entries[uid] = AttributeDict({
427 c.issuetracker_entries[uid] = AttributeDict({
427 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
428 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
428 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
429 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
429 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
430 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
430 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
431 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
431 })
432 })
432
433
433 return self._get_template_context(c)
434 return self._get_template_context(c)
434
435
435 @LoginRequired()
436 @LoginRequired()
436 @HasPermissionAllDecorator('hg.admin')
437 @HasPermissionAllDecorator('hg.admin')
437 @CSRFRequired()
438 @CSRFRequired()
438 def settings_issuetracker_test(self):
439 def settings_issuetracker_test(self):
439 error_container = []
440 error_container = []
440
441
441 urlified_commit = h.urlify_commit_message(
442 urlified_commit = h.urlify_commit_message(
442 self.request.POST.get('test_text', ''),
443 self.request.POST.get('test_text', ''),
443 'repo_group/test_repo1', error_container=error_container)
444 'repo_group/test_repo1', error_container=error_container)
444 if error_container:
445 if error_container:
445 def converter(inp):
446 def converter(inp):
446 return h.html_escape(inp)
447 return h.html_escape(inp)
447
448
448 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
449 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
449
450
450 return urlified_commit
451 return urlified_commit
451
452
452 @LoginRequired()
453 @LoginRequired()
453 @HasPermissionAllDecorator('hg.admin')
454 @HasPermissionAllDecorator('hg.admin')
454 @CSRFRequired()
455 @CSRFRequired()
455 def settings_issuetracker_update(self):
456 def settings_issuetracker_update(self):
456 _ = self.request.translate
457 _ = self.request.translate
457 self.load_default_context()
458 self.load_default_context()
458 settings_model = IssueTrackerSettingsModel()
459 settings_model = IssueTrackerSettingsModel()
459
460
460 try:
461 try:
461 form = IssueTrackerPatternsForm(self.request.translate)()
462 form = IssueTrackerPatternsForm(self.request.translate)()
462 data = form.to_python(self.request.POST)
463 data = form.to_python(self.request.POST)
463 except formencode.Invalid as errors:
464 except formencode.Invalid as errors:
464 log.exception('Failed to add new pattern')
465 log.exception('Failed to add new pattern')
465 error = errors
466 error = errors
466 h.flash(_(f'Invalid issue tracker pattern: {error}'),
467 h.flash(_(f'Invalid issue tracker pattern: {error}'),
467 category='error')
468 category='error')
468 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
469 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
469
470
470 if data:
471 if data:
471 for uid in data.get('delete_patterns', []):
472 for uid in data.get('delete_patterns', []):
472 settings_model.delete_entries(uid)
473 settings_model.delete_entries(uid)
473
474
474 for pattern in data.get('patterns', []):
475 for pattern in data.get('patterns', []):
475 for setting, value, type_ in pattern:
476 for setting, value, type_ in pattern:
476 sett = settings_model.create_or_update_setting(
477 sett = settings_model.create_or_update_setting(
477 setting, value, type_)
478 setting, value, type_)
478 Session().add(sett)
479 Session().add(sett)
479
480
480 Session().commit()
481 Session().commit()
481
482
482 SettingsModel().invalidate_settings_cache()
483 SettingsModel().invalidate_settings_cache()
483 h.flash(_('Updated issue tracker entries'), category='success')
484 h.flash(_('Updated issue tracker entries'), category='success')
484 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
485 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
485
486
486 @LoginRequired()
487 @LoginRequired()
487 @HasPermissionAllDecorator('hg.admin')
488 @HasPermissionAllDecorator('hg.admin')
488 @CSRFRequired()
489 @CSRFRequired()
489 def settings_issuetracker_delete(self):
490 def settings_issuetracker_delete(self):
490 _ = self.request.translate
491 _ = self.request.translate
491 self.load_default_context()
492 self.load_default_context()
492 uid = self.request.POST.get('uid')
493 uid = self.request.POST.get('uid')
493 try:
494 try:
494 IssueTrackerSettingsModel().delete_entries(uid)
495 IssueTrackerSettingsModel().delete_entries(uid)
495 except Exception:
496 except Exception:
496 log.exception('Failed to delete issue tracker setting %s', uid)
497 log.exception('Failed to delete issue tracker setting %s', uid)
497 raise HTTPNotFound()
498 raise HTTPNotFound()
498
499
499 SettingsModel().invalidate_settings_cache()
500 SettingsModel().invalidate_settings_cache()
500 h.flash(_('Removed issue tracker entry.'), category='success')
501 h.flash(_('Removed issue tracker entry.'), category='success')
501
502
502 return {'deleted': uid}
503 return {'deleted': uid}
503
504
504 @LoginRequired()
505 @LoginRequired()
505 @HasPermissionAllDecorator('hg.admin')
506 @HasPermissionAllDecorator('hg.admin')
506 def settings_email(self):
507 def settings_email(self):
507 c = self.load_default_context()
508 c = self.load_default_context()
508 c.active = 'email'
509 c.active = 'email'
509 c.rhodecode_ini = rhodecode.CONFIG
510 c.rhodecode_ini = rhodecode.CONFIG
510
511
511 data = render('rhodecode:templates/admin/settings/settings.mako',
512 data = render('rhodecode:templates/admin/settings/settings.mako',
512 self._get_template_context(c), self.request)
513 self._get_template_context(c), self.request)
513 html = formencode.htmlfill.render(
514 html = formencode.htmlfill.render(
514 data,
515 data,
515 defaults=self._form_defaults(),
516 defaults=self._form_defaults(),
516 encoding="UTF-8",
517 encoding="UTF-8",
517 force_defaults=False
518 force_defaults=False
518 )
519 )
519 return Response(html)
520 return Response(html)
520
521
521 @LoginRequired()
522 @LoginRequired()
522 @HasPermissionAllDecorator('hg.admin')
523 @HasPermissionAllDecorator('hg.admin')
523 @CSRFRequired()
524 @CSRFRequired()
524 def settings_email_update(self):
525 def settings_email_update(self):
525 _ = self.request.translate
526 _ = self.request.translate
526 c = self.load_default_context()
527 c = self.load_default_context()
527 c.active = 'email'
528 c.active = 'email'
528
529
529 test_email = self.request.POST.get('test_email')
530 test_email = self.request.POST.get('test_email')
530
531
531 if not test_email:
532 if not test_email:
532 h.flash(_('Please enter email address'), category='error')
533 h.flash(_('Please enter email address'), category='error')
533 raise HTTPFound(h.route_path('admin_settings_email'))
534 raise HTTPFound(h.route_path('admin_settings_email'))
534
535
535 email_kwargs = {
536 email_kwargs = {
536 'date': datetime.datetime.now(),
537 'date': datetime.datetime.now(),
537 'user': self._rhodecode_db_user
538 'user': self._rhodecode_db_user
538 }
539 }
539
540
540 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
541 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
541 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
542 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
542
543
543 recipients = [test_email] if test_email else None
544 recipients = [test_email] if test_email else None
544
545
545 run_task(tasks.send_email, recipients, subject,
546 run_task(tasks.send_email, recipients, subject,
546 email_body_plaintext, email_body)
547 email_body_plaintext, email_body)
547
548
548 h.flash(_('Send email task created'), category='success')
549 h.flash(_('Send email task created'), category='success')
549 raise HTTPFound(h.route_path('admin_settings_email'))
550 raise HTTPFound(h.route_path('admin_settings_email'))
550
551
551 @LoginRequired()
552 @LoginRequired()
552 @HasPermissionAllDecorator('hg.admin')
553 @HasPermissionAllDecorator('hg.admin')
553 def settings_hooks(self):
554 def settings_hooks(self):
554 c = self.load_default_context()
555 c = self.load_default_context()
555 c.active = 'hooks'
556 c.active = 'hooks'
556
557
557 model = SettingsModel()
558 model = SettingsModel()
558 c.hooks = model.get_builtin_hooks()
559 c.hooks = model.get_builtin_hooks()
559 c.custom_hooks = model.get_custom_hooks()
560 c.custom_hooks = model.get_custom_hooks()
560
561
561 data = render('rhodecode:templates/admin/settings/settings.mako',
562 data = render('rhodecode:templates/admin/settings/settings.mako',
562 self._get_template_context(c), self.request)
563 self._get_template_context(c), self.request)
563 html = formencode.htmlfill.render(
564 html = formencode.htmlfill.render(
564 data,
565 data,
565 defaults=self._form_defaults(),
566 defaults=self._form_defaults(),
566 encoding="UTF-8",
567 encoding="UTF-8",
567 force_defaults=False
568 force_defaults=False
568 )
569 )
569 return Response(html)
570 return Response(html)
570
571
571 @LoginRequired()
572 @LoginRequired()
572 @HasPermissionAllDecorator('hg.admin')
573 @HasPermissionAllDecorator('hg.admin')
573 @CSRFRequired()
574 @CSRFRequired()
574 def settings_hooks_update(self):
575 def settings_hooks_update(self):
575 _ = self.request.translate
576 _ = self.request.translate
576 c = self.load_default_context()
577 c = self.load_default_context()
577 c.active = 'hooks'
578 c.active = 'hooks'
578 if c.visual.allow_custom_hooks_settings:
579 if c.visual.allow_custom_hooks_settings:
579 ui_key = self.request.POST.get('new_hook_ui_key')
580 ui_key = self.request.POST.get('new_hook_ui_key')
580 ui_value = self.request.POST.get('new_hook_ui_value')
581 ui_value = self.request.POST.get('new_hook_ui_value')
581
582
582 hook_id = self.request.POST.get('hook_id')
583 hook_id = self.request.POST.get('hook_id')
583 new_hook = False
584 new_hook = False
584
585
585 model = SettingsModel()
586 model = SettingsModel()
586 try:
587 try:
587 if ui_value and ui_key:
588 if ui_value and ui_key:
588 model.create_or_update_hook(ui_key, ui_value)
589 model.create_or_update_hook(ui_key, ui_value)
589 h.flash(_('Added new hook'), category='success')
590 h.flash(_('Added new hook'), category='success')
590 new_hook = True
591 new_hook = True
591 elif hook_id:
592 elif hook_id:
592 RhodeCodeUi.delete(hook_id)
593 RhodeCodeUi.delete(hook_id)
593 Session().commit()
594 Session().commit()
594
595
595 # check for edits
596 # check for edits
596 update = False
597 update = False
597 _d = self.request.POST.dict_of_lists()
598 _d = self.request.POST.dict_of_lists()
598 for k, v in zip(_d.get('hook_ui_key', []),
599 for k, v in zip(_d.get('hook_ui_key', []),
599 _d.get('hook_ui_value_new', [])):
600 _d.get('hook_ui_value_new', [])):
600 model.create_or_update_hook(k, v)
601 model.create_or_update_hook(k, v)
601 update = True
602 update = True
602
603
603 if update and not new_hook:
604 if update and not new_hook:
604 h.flash(_('Updated hooks'), category='success')
605 h.flash(_('Updated hooks'), category='success')
605 Session().commit()
606 Session().commit()
606 except Exception:
607 except Exception:
607 log.exception("Exception during hook creation")
608 log.exception("Exception during hook creation")
608 h.flash(_('Error occurred during hook creation'),
609 h.flash(_('Error occurred during hook creation'),
609 category='error')
610 category='error')
610
611
611 raise HTTPFound(h.route_path('admin_settings_hooks'))
612 raise HTTPFound(h.route_path('admin_settings_hooks'))
612
613
613 @LoginRequired()
614 @LoginRequired()
614 @HasPermissionAllDecorator('hg.admin')
615 @HasPermissionAllDecorator('hg.admin')
615 def settings_search(self):
616 def settings_search(self):
616 c = self.load_default_context()
617 c = self.load_default_context()
617 c.active = 'search'
618 c.active = 'search'
618
619
619 c.searcher = searcher_from_config(self.request.registry.settings)
620 c.searcher = searcher_from_config(self.request.registry.settings)
620 c.statistics = c.searcher.statistics(self.request.translate)
621 c.statistics = c.searcher.statistics(self.request.translate)
621
622
622 return self._get_template_context(c)
623 return self._get_template_context(c)
623
624
624 @LoginRequired()
625 @LoginRequired()
625 @HasPermissionAllDecorator('hg.admin')
626 @HasPermissionAllDecorator('hg.admin')
626 def settings_automation(self):
627 c = self.load_default_context()
628 c.active = 'automation'
629
630 return self._get_template_context(c)
631
632 @LoginRequired()
633 @HasPermissionAllDecorator('hg.admin')
634 def settings_labs(self):
627 def settings_labs(self):
635 c = self.load_default_context()
628 c = self.load_default_context()
636 if not c.labs_active:
629 if not c.labs_active:
637 raise HTTPFound(h.route_path('admin_settings'))
630 raise HTTPFound(h.route_path('admin_settings'))
638
631
639 c.active = 'labs'
632 c.active = 'labs'
640 c.lab_settings = _LAB_SETTINGS
633 c.lab_settings = _LAB_SETTINGS
641
634
642 data = render('rhodecode:templates/admin/settings/settings.mako',
635 data = render('rhodecode:templates/admin/settings/settings.mako',
643 self._get_template_context(c), self.request)
636 self._get_template_context(c), self.request)
644 html = formencode.htmlfill.render(
637 html = formencode.htmlfill.render(
645 data,
638 data,
646 defaults=self._form_defaults(),
639 defaults=self._form_defaults(),
647 encoding="UTF-8",
640 encoding="UTF-8",
648 force_defaults=False
641 force_defaults=False
649 )
642 )
650 return Response(html)
643 return Response(html)
651
644
652 @LoginRequired()
645 @LoginRequired()
653 @HasPermissionAllDecorator('hg.admin')
646 @HasPermissionAllDecorator('hg.admin')
654 @CSRFRequired()
647 @CSRFRequired()
655 def settings_labs_update(self):
648 def settings_labs_update(self):
656 _ = self.request.translate
649 _ = self.request.translate
657 c = self.load_default_context()
650 c = self.load_default_context()
658 c.active = 'labs'
651 c.active = 'labs'
659
652
660 application_form = LabsSettingsForm(self.request.translate)()
653 application_form = LabsSettingsForm(self.request.translate)()
661 try:
654 try:
662 form_result = application_form.to_python(dict(self.request.POST))
655 form_result = application_form.to_python(dict(self.request.POST))
663 except formencode.Invalid as errors:
656 except formencode.Invalid as errors:
664 h.flash(
657 h.flash(
665 _("Some form inputs contain invalid data."),
658 _("Some form inputs contain invalid data."),
666 category='error')
659 category='error')
667 data = render('rhodecode:templates/admin/settings/settings.mako',
660 data = render('rhodecode:templates/admin/settings/settings.mako',
668 self._get_template_context(c), self.request)
661 self._get_template_context(c), self.request)
669 html = formencode.htmlfill.render(
662 html = formencode.htmlfill.render(
670 data,
663 data,
671 defaults=errors.value,
664 defaults=errors.value,
672 errors=errors.unpack_errors() or {},
665 errors=errors.unpack_errors() or {},
673 prefix_error=False,
666 prefix_error=False,
674 encoding="UTF-8",
667 encoding="UTF-8",
675 force_defaults=False
668 force_defaults=False
676 )
669 )
677 return Response(html)
670 return Response(html)
678
671
679 try:
672 try:
680 session = Session()
673 session = Session()
681 for setting in _LAB_SETTINGS:
674 for setting in _LAB_SETTINGS:
682 setting_name = setting.key[len('rhodecode_'):]
675 setting_name = setting.key[len('rhodecode_'):]
683 sett = SettingsModel().create_or_update_setting(
676 sett = SettingsModel().create_or_update_setting(
684 setting_name, form_result[setting.key], setting.type)
677 setting_name, form_result[setting.key], setting.type)
685 session.add(sett)
678 session.add(sett)
686
679
687 except Exception:
680 except Exception:
688 log.exception('Exception while updating lab settings')
681 log.exception('Exception while updating lab settings')
689 h.flash(_('Error occurred during updating labs settings'),
682 h.flash(_('Error occurred during updating labs settings'),
690 category='error')
683 category='error')
691 else:
684 else:
692 Session().commit()
685 Session().commit()
693 SettingsModel().invalidate_settings_cache()
686 SettingsModel().invalidate_settings_cache()
694 h.flash(_('Updated Labs settings'), category='success')
687 h.flash(_('Updated Labs settings'), category='success')
695 raise HTTPFound(h.route_path('admin_settings_labs'))
688 raise HTTPFound(h.route_path('admin_settings_labs'))
696
689
697 data = render('rhodecode:templates/admin/settings/settings.mako',
690 data = render('rhodecode:templates/admin/settings/settings.mako',
698 self._get_template_context(c), self.request)
691 self._get_template_context(c), self.request)
699 html = formencode.htmlfill.render(
692 html = formencode.htmlfill.render(
700 data,
693 data,
701 defaults=self._form_defaults(),
694 defaults=self._form_defaults(),
702 encoding="UTF-8",
695 encoding="UTF-8",
703 force_defaults=False
696 force_defaults=False
704 )
697 )
705 return Response(html)
698 return Response(html)
706
699
707
700
708 # :param key: name of the setting including the 'rhodecode_' prefix
701 # :param key: name of the setting including the 'rhodecode_' prefix
709 # :param type: the RhodeCodeSetting type to use.
702 # :param type: the RhodeCodeSetting type to use.
710 # :param group: the i18ned group in which we should dispaly this setting
703 # :param group: the i18ned group in which we should dispaly this setting
711 # :param label: the i18ned label we should display for this setting
704 # :param label: the i18ned label we should display for this setting
712 # :param help: the i18ned help we should dispaly for this setting
705 # :param help: the i18ned help we should dispaly for this setting
713 LabSetting = collections.namedtuple(
706 LabSetting = collections.namedtuple(
714 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
707 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
715
708
716
709
717 # This list has to be kept in sync with the form
710 # This list has to be kept in sync with the form
718 # rhodecode.model.forms.LabsSettingsForm.
711 # rhodecode.model.forms.LabsSettingsForm.
719 _LAB_SETTINGS = [
712 _LAB_SETTINGS = [
720
713
721 ]
714 ]
@@ -1,1582 +1,1583 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import itertools
19 import itertools
20 import logging
20 import logging
21 import os
21 import os
22 import collections
22 import collections
23 import urllib.request
23 import urllib.request
24 import urllib.parse
24 import urllib.parse
25 import urllib.error
25 import urllib.error
26 import pathlib
26 import pathlib
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29
29
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.apps._base import RepoAppView
34 from rhodecode.apps._base import RepoAppView
35
35
36
36
37 from rhodecode.lib import diffs, helpers as h, rc_cache
37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.hash_utils import sha1_safe
39 from rhodecode.lib.hash_utils import sha1_safe
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
42 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.view_utils import parse_path_ref
43 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.exceptions import NonRelativePathError
44 from rhodecode.lib.codeblocks import (
44 from rhodecode.lib.codeblocks import (
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
47 from rhodecode.lib.type_utils import str2bool
47 from rhodecode.lib.type_utils import str2bool
48 from rhodecode.lib.str_utils import safe_str, safe_int
48 from rhodecode.lib.str_utils import safe_str, safe_int
49 from rhodecode.lib.auth import (
49 from rhodecode.lib.auth import (
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
51 from rhodecode.lib.vcs import path as vcspath
51 from rhodecode.lib.vcs import path as vcspath
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
53 from rhodecode.lib.vcs.conf import settings
53 from rhodecode.lib.vcs.conf import settings
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55 from rhodecode.lib.vcs.exceptions import (
55 from rhodecode.lib.vcs.exceptions import (
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
58 NodeDoesNotExistError, CommitError, NodeError)
58 NodeDoesNotExistError, CommitError, NodeError)
59
59
60 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.db import Repository
61 from rhodecode.model.db import Repository
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
67 # original backward compat name of archive
67 # original backward compat name of archive
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
69
69
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
72 id_sha = sha1_safe(str(db_repo_id))[:4]
72 id_sha = sha1_safe(str(db_repo_id))[:4]
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
74 commit = commit_sha if with_hash else 'archive'
74 commit = commit_sha if with_hash else 'archive'
75 path_marker = (path_sha if with_hash else '') or 'all'
75 path_marker = (path_sha if with_hash else '') or 'all'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
77
77
78 return archive_name
78 return archive_name
79
79
80
80
81 def get_path_sha(at_path):
81 def get_path_sha(at_path):
82 return safe_str(sha1_safe(at_path)[:8])
82 return safe_str(sha1_safe(at_path)[:8])
83
83
84
84
85 def _get_archive_spec(fname):
85 def _get_archive_spec(fname):
86 log.debug('Detecting archive spec for: `%s`', fname)
86 log.debug('Detecting archive spec for: `%s`', fname)
87
87
88 fileformat = None
88 fileformat = None
89 ext = None
89 ext = None
90 content_type = None
90 content_type = None
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
92
92
93 if fname.endswith(extension):
93 if fname.endswith(extension):
94 fileformat = a_type
94 fileformat = a_type
95 log.debug('archive is of type: %s', fileformat)
95 log.debug('archive is of type: %s', fileformat)
96 ext = extension
96 ext = extension
97 break
97 break
98
98
99 if not fileformat:
99 if not fileformat:
100 raise ValueError()
100 raise ValueError()
101
101
102 # left over part of whole fname is the commit
102 # left over part of whole fname is the commit
103 commit_id = fname[:-len(ext)]
103 commit_id = fname[:-len(ext)]
104
104
105 return commit_id, ext, fileformat, content_type
105 return commit_id, ext, fileformat, content_type
106
106
107
107
108 class RepoFilesView(RepoAppView):
108 class RepoFilesView(RepoAppView):
109
109
110 @staticmethod
110 @staticmethod
111 def adjust_file_path_for_svn(f_path, repo):
111 def adjust_file_path_for_svn(f_path, repo):
112 """
112 """
113 Computes the relative path of `f_path`.
113 Computes the relative path of `f_path`.
114
114
115 This is mainly based on prefix matching of the recognized tags and
115 This is mainly based on prefix matching of the recognized tags and
116 branches in the underlying repository.
116 branches in the underlying repository.
117 """
117 """
118 tags_and_branches = itertools.chain(
118 tags_and_branches = itertools.chain(
119 repo.branches.keys(),
119 repo.branches.keys(),
120 repo.tags.keys())
120 repo.tags.keys())
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
122
122
123 for name in tags_and_branches:
123 for name in tags_and_branches:
124 if f_path.startswith(f'{name}/'):
124 if f_path.startswith(f'{name}/'):
125 f_path = vcspath.relpath(f_path, name)
125 f_path = vcspath.relpath(f_path, name)
126 break
126 break
127 return f_path
127 return f_path
128
128
129 def load_default_context(self):
129 def load_default_context(self):
130 c = self._get_local_tmpl_context(include_app_defaults=True)
130 c = self._get_local_tmpl_context(include_app_defaults=True)
131 c.rhodecode_repo = self.rhodecode_vcs_repo
131 c.rhodecode_repo = self.rhodecode_vcs_repo
132 c.enable_downloads = self.db_repo.enable_downloads
132 c.enable_downloads = self.db_repo.enable_downloads
133 return c
133 return c
134
134
135 def _ensure_not_locked(self, commit_id='tip'):
135 def _ensure_not_locked(self, commit_id='tip'):
136 _ = self.request.translate
136 _ = self.request.translate
137
137
138 repo = self.db_repo
138 repo = self.db_repo
139 if repo.enable_locking and repo.locked[0]:
139 if repo.enable_locking and repo.locked[0]:
140 h.flash(_('This repository has been locked by %s on %s')
140 h.flash(_('This repository has been locked by %s on %s')
141 % (h.person_by_id(repo.locked[0]),
141 % (h.person_by_id(repo.locked[0]),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
143 'warning')
143 'warning')
144 files_url = h.route_path(
144 files_url = h.route_path(
145 'repo_files:default_path',
145 'repo_files:default_path',
146 repo_name=self.db_repo_name, commit_id=commit_id)
146 repo_name=self.db_repo_name, commit_id=commit_id)
147 raise HTTPFound(files_url)
147 raise HTTPFound(files_url)
148
148
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
150 _ = self.request.translate
150 _ = self.request.translate
151
151
152 if not is_head:
152 if not is_head:
153 message = _('Cannot modify file. '
153 message = _('Cannot modify file. '
154 'Given commit `{}` is not head of a branch.').format(commit_id)
154 'Given commit `{}` is not head of a branch.').format(commit_id)
155 h.flash(message, category='warning')
155 h.flash(message, category='warning')
156
156
157 if json_mode:
157 if json_mode:
158 return message
158 return message
159
159
160 files_url = h.route_path(
160 files_url = h.route_path(
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
162 f_path=f_path)
162 f_path=f_path)
163 raise HTTPFound(files_url)
163 raise HTTPFound(files_url)
164
164
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
166 _ = self.request.translate
166 _ = self.request.translate
167
167
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
169 self.db_repo_name, branch_name)
169 self.db_repo_name, branch_name)
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
172 h.escape(branch_name), h.escape(rule))
172 h.escape(branch_name), h.escape(rule))
173 h.flash(message, 'warning')
173 h.flash(message, 'warning')
174
174
175 if json_mode:
175 if json_mode:
176 return message
176 return message
177
177
178 files_url = h.route_path(
178 files_url = h.route_path(
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
180
180
181 raise HTTPFound(files_url)
181 raise HTTPFound(files_url)
182
182
183 def _get_commit_and_path(self):
183 def _get_commit_and_path(self):
184 default_commit_id = self.db_repo.landing_ref_name
184 default_commit_id = self.db_repo.landing_ref_name
185 default_f_path = '/'
185 default_f_path = '/'
186
186
187 commit_id = self.request.matchdict.get(
187 commit_id = self.request.matchdict.get(
188 'commit_id', default_commit_id)
188 'commit_id', default_commit_id)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
190 return commit_id, f_path
190 return commit_id, f_path
191
191
192 def _get_default_encoding(self, c):
192 def _get_default_encoding(self, c):
193 enc_list = getattr(c, 'default_encodings', [])
193 enc_list = getattr(c, 'default_encodings', [])
194 return enc_list[0] if enc_list else 'UTF-8'
194 return enc_list[0] if enc_list else 'UTF-8'
195
195
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
197 """
197 """
198 This is a safe way to get commit. If an error occurs it redirects to
198 This is a safe way to get commit. If an error occurs it redirects to
199 tip with proper message
199 tip with proper message
200
200
201 :param commit_id: id of commit to fetch
201 :param commit_id: id of commit to fetch
202 :param redirect_after: toggle redirection
202 :param redirect_after: toggle redirection
203 """
203 """
204 _ = self.request.translate
204 _ = self.request.translate
205
205
206 try:
206 try:
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
208 except EmptyRepositoryError:
208 except EmptyRepositoryError:
209 if not redirect_after:
209 if not redirect_after:
210 return None
210 return None
211
211
212 add_new = upload_new = ""
212 add_new = upload_new = ""
213 if h.HasRepoPermissionAny(
213 if h.HasRepoPermissionAny(
214 'repository.write', 'repository.admin')(self.db_repo_name):
214 'repository.write', 'repository.admin')(self.db_repo_name):
215 _url = h.route_path(
215 _url = h.route_path(
216 'repo_files_add_file',
216 'repo_files_add_file',
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
218 add_new = h.link_to(
218 add_new = h.link_to(
219 _('add a new file'), _url, class_="alert-link")
219 _('add a new file'), _url, class_="alert-link")
220
220
221 _url_upld = h.route_path(
221 _url_upld = h.route_path(
222 'repo_files_upload_file',
222 'repo_files_upload_file',
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
224 upload_new = h.link_to(
224 upload_new = h.link_to(
225 _('upload a new file'), _url_upld, class_="alert-link")
225 _('upload a new file'), _url_upld, class_="alert-link")
226
226
227 h.flash(h.literal(
227 h.flash(h.literal(
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
229 raise HTTPFound(
229 raise HTTPFound(
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
231
231
232 except (CommitDoesNotExistError, LookupError) as e:
232 except (CommitDoesNotExistError, LookupError) as e:
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
234 h.flash(msg, category='error')
234 h.flash(msg, category='error')
235 raise HTTPNotFound()
235 raise HTTPNotFound()
236 except RepositoryError as e:
236 except RepositoryError as e:
237 h.flash(h.escape(safe_str(e)), category='error')
237 h.flash(h.escape(safe_str(e)), category='error')
238 raise HTTPNotFound()
238 raise HTTPNotFound()
239
239
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
241 """
241 """
242 Returns file_node, if error occurs or given path is directory,
242 Returns file_node, if error occurs or given path is directory,
243 it'll redirect to top level path
243 it'll redirect to top level path
244 """
244 """
245 _ = self.request.translate
245 _ = self.request.translate
246
246
247 try:
247 try:
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
249 if file_node.is_dir():
249 if file_node.is_dir():
250 raise RepositoryError('The given path is a directory')
250 raise RepositoryError('The given path is a directory')
251 except CommitDoesNotExistError:
251 except CommitDoesNotExistError:
252 log.exception('No such commit exists for this repository')
252 log.exception('No such commit exists for this repository')
253 h.flash(_('No such commit exists for this repository'), category='error')
253 h.flash(_('No such commit exists for this repository'), category='error')
254 raise HTTPNotFound()
254 raise HTTPNotFound()
255 except RepositoryError as e:
255 except RepositoryError as e:
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
257 h.flash(h.escape(safe_str(e)), category='error')
257 h.flash(h.escape(safe_str(e)), category='error')
258 raise HTTPNotFound()
258 raise HTTPNotFound()
259
259
260 return file_node
260 return file_node
261
261
262 def _is_valid_head(self, commit_id, repo, landing_ref):
262 def _is_valid_head(self, commit_id, repo, landing_ref):
263 branch_name = sha_commit_id = ''
263 branch_name = sha_commit_id = ''
264 is_head = False
264 is_head = False
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
266
266
267 for _branch_name, branch_commit_id in repo.branches.items():
267 for _branch_name, branch_commit_id in repo.branches.items():
268 # simple case we pass in branch name, it's a HEAD
268 # simple case we pass in branch name, it's a HEAD
269 if commit_id == _branch_name:
269 if commit_id == _branch_name:
270 is_head = True
270 is_head = True
271 branch_name = _branch_name
271 branch_name = _branch_name
272 sha_commit_id = branch_commit_id
272 sha_commit_id = branch_commit_id
273 break
273 break
274 # case when we pass in full sha commit_id, which is a head
274 # case when we pass in full sha commit_id, which is a head
275 elif commit_id == branch_commit_id:
275 elif commit_id == branch_commit_id:
276 is_head = True
276 is_head = True
277 branch_name = _branch_name
277 branch_name = _branch_name
278 sha_commit_id = branch_commit_id
278 sha_commit_id = branch_commit_id
279 break
279 break
280
280
281 if h.is_svn(repo) and not repo.is_empty():
281 if h.is_svn(repo) and not repo.is_empty():
282 # Note: Subversion only has one head.
282 # Note: Subversion only has one head.
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
284 is_head = True
284 is_head = True
285 return branch_name, sha_commit_id, is_head
285 return branch_name, sha_commit_id, is_head
286
286
287 # checked branches, means we only need to try to get the branch/commit_sha
287 # checked branches, means we only need to try to get the branch/commit_sha
288 if repo.is_empty():
288 if repo.is_empty():
289 is_head = True
289 is_head = True
290 branch_name = landing_ref
290 branch_name = landing_ref
291 sha_commit_id = EmptyCommit().raw_id
291 sha_commit_id = EmptyCommit().raw_id
292 else:
292 else:
293 commit = repo.get_commit(commit_id=commit_id)
293 commit = repo.get_commit(commit_id=commit_id)
294 if commit:
294 if commit:
295 branch_name = commit.branch
295 branch_name = commit.branch
296 sha_commit_id = commit.raw_id
296 sha_commit_id = commit.raw_id
297
297
298 return branch_name, sha_commit_id, is_head
298 return branch_name, sha_commit_id, is_head
299
299
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
301
301
302 repo_id = self.db_repo.repo_id
302 repo_id = self.db_repo.repo_id
303 force_recache = self.get_recache_flag()
303 force_recache = self.get_recache_flag()
304
304
305 cache_seconds = safe_int(
305 cache_seconds = safe_int(
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
307 cache_on = not force_recache and cache_seconds > 0
307 cache_on = not force_recache and cache_seconds > 0
308 log.debug(
308 log.debug(
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
310 'with caching: %s[TTL: %ss]' % (
310 'with caching: %s[TTL: %ss]' % (
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
312
312
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
315
315
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
319 _repo_id, _commit_id, _f_path)
319 _repo_id, _commit_id, _f_path)
320
320
321 c.full_load = _full_load
321 c.full_load = _full_load
322 return render(
322 return render(
323 'rhodecode:templates/files/files_browser_tree.mako',
323 'rhodecode:templates/files/files_browser_tree.mako',
324 self._get_template_context(c), self.request, _at_rev)
324 self._get_template_context(c), self.request, _at_rev)
325
325
326 return compute_file_tree(
326 return compute_file_tree(
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
328
328
329 def create_pure_path(self, *parts):
329 def create_pure_path(self, *parts):
330 # Split paths and sanitize them, removing any ../ etc
330 # Split paths and sanitize them, removing any ../ etc
331 sanitized_path = [
331 sanitized_path = [
332 x for x in pathlib.PurePath(*parts).parts
332 x for x in pathlib.PurePath(*parts).parts
333 if x not in ['.', '..']]
333 if x not in ['.', '..']]
334
334
335 pure_path = pathlib.PurePath(*sanitized_path)
335 pure_path = pathlib.PurePath(*sanitized_path)
336 return pure_path
336 return pure_path
337
337
338 def _is_lf_enabled(self, target_repo):
338 def _is_lf_enabled(self, target_repo):
339 lf_enabled = False
339 lf_enabled = False
340
340
341 lf_key_for_vcs_map = {
341 lf_key_for_vcs_map = {
342 'hg': 'extensions_largefiles',
342 'hg': 'extensions_largefiles',
343 'git': 'vcs_git_lfs_enabled'
343 'git': 'vcs_git_lfs_enabled'
344 }
344 }
345
345
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
347
347
348 if lf_key_for_vcs:
348 if lf_key_for_vcs:
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
350
350
351 return lf_enabled
351 return lf_enabled
352
352
353 @LoginRequired()
353 @LoginRequired()
354 @HasRepoPermissionAnyDecorator(
354 @HasRepoPermissionAnyDecorator(
355 'repository.read', 'repository.write', 'repository.admin')
355 'repository.read', 'repository.write', 'repository.admin')
356 def repo_archivefile(self):
356 def repo_archivefile(self):
357 # archive cache config
357 # archive cache config
358 from rhodecode import CONFIG
358 from rhodecode import CONFIG
359 _ = self.request.translate
359 _ = self.request.translate
360 self.load_default_context()
360 self.load_default_context()
361 default_at_path = '/'
361 default_at_path = '/'
362 fname = self.request.matchdict['fname']
362 fname = self.request.matchdict['fname']
363 subrepos = self.request.GET.get('subrepos') == 'true'
363 subrepos = self.request.GET.get('subrepos') == 'true'
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
365 at_path = self.request.GET.get('at_path') or default_at_path
365 at_path = self.request.GET.get('at_path') or default_at_path
366
366
367 if not self.db_repo.enable_downloads:
367 if not self.db_repo.enable_downloads:
368 return Response(_('Downloads disabled'))
368 return Response(_('Downloads disabled'))
369
369
370 try:
370 try:
371 commit_id, ext, fileformat, content_type = \
371 commit_id, ext, fileformat, content_type = \
372 _get_archive_spec(fname)
372 _get_archive_spec(fname)
373 except ValueError:
373 except ValueError:
374 return Response(_('Unknown archive type for: `{}`').format(
374 return Response(_('Unknown archive type for: `{}`').format(
375 h.escape(fname)))
375 h.escape(fname)))
376
376
377 try:
377 try:
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
379 except CommitDoesNotExistError:
379 except CommitDoesNotExistError:
380 return Response(_('Unknown commit_id {}').format(
380 return Response(_('Unknown commit_id {}').format(
381 h.escape(commit_id)))
381 h.escape(commit_id)))
382 except EmptyRepositoryError:
382 except EmptyRepositoryError:
383 return Response(_('Empty repository'))
383 return Response(_('Empty repository'))
384
384
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
386 if commit_id != commit.raw_id:
386 if commit_id != commit.raw_id:
387 fname=f'{commit.raw_id}{ext}'
387 fname=f'{commit.raw_id}{ext}'
388 raise HTTPFound(self.request.current_route_path(fname=fname))
388 raise HTTPFound(self.request.current_route_path(fname=fname))
389
389
390 try:
390 try:
391 at_path = commit.get_node(at_path).path or default_at_path
391 at_path = commit.get_node(at_path).path or default_at_path
392 except Exception:
392 except Exception:
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
394
394
395 path_sha = get_path_sha(at_path)
395 path_sha = get_path_sha(at_path)
396
396
397 # used for cache etc, consistent unique archive name
397 # used for cache etc, consistent unique archive name
398 archive_name_key = get_archive_name(
398 archive_name_key = get_archive_name(
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
400 path_sha=path_sha, with_hash=True)
400 path_sha=path_sha, with_hash=True)
401
401
402 if not with_hash:
402 if not with_hash:
403 path_sha = ''
403 path_sha = ''
404
404
405 # what end client gets served
405 # what end client gets served
406 response_archive_name = get_archive_name(
406 response_archive_name = get_archive_name(
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
408 path_sha=path_sha, with_hash=with_hash)
408 path_sha=path_sha, with_hash=with_hash)
409
409
410 # remove extension from our archive directory name
410 # remove extension from our archive directory name
411 archive_dir_name = response_archive_name[:-len(ext)]
411 archive_dir_name = response_archive_name[:-len(ext)]
412
412
413 archive_cache_disable = self.request.GET.get('no_cache')
413 archive_cache_disable = self.request.GET.get('no_cache')
414
414
415 d_cache = get_archival_cache_store(config=CONFIG)
415 d_cache = get_archival_cache_store(config=CONFIG)
416
416 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 d_cache_conf = get_archival_config(config=CONFIG)
418 d_cache_conf = get_archival_config(config=CONFIG)
418
419
419 reentrant_lock_key = archive_name_key + '.lock'
420 reentrant_lock_key = archive_name_key + '.lock'
420 with ReentrantLock(d_cache, reentrant_lock_key):
421 with ReentrantLock(d_cache, reentrant_lock_key):
421 # This is also a cache key
422 # This is also a cache key
422 use_cached_archive = False
423 use_cached_archive = False
423 if archive_name_key in d_cache and not archive_cache_disable:
424 if archive_name_key in d_cache and not archive_cache_disable:
424 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 use_cached_archive = True
426 use_cached_archive = True
426 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 archive_name_key, tag, reader.name)
428 archive_name_key, tag, reader.name)
428 else:
429 else:
429 reader = None
430 reader = None
430 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431
432
432 # generate new archive, as previous was not found in the cache
433 # generate new archive, as previous was not found in the cache
433 if not reader:
434 if not reader:
434
435
435 try:
436 try:
436 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
437 kind=fileformat, subrepos=subrepos,
438 kind=fileformat, subrepos=subrepos,
438 archive_at_path=at_path, cache_config=d_cache_conf)
439 archive_at_path=at_path, cache_config=d_cache_conf)
439 except ImproperArchiveTypeError:
440 except ImproperArchiveTypeError:
440 return _('Unknown archive type')
441 return _('Unknown archive type')
441
442
442 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
443
444
444 if not reader:
445 if not reader:
445 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
446
447
447 def archive_iterator(_reader, block_size: int = 4096*512):
448 def archive_iterator(_reader, block_size: int = 4096*512):
448 # 4096 * 64 = 64KB
449 # 4096 * 64 = 64KB
449 while 1:
450 while 1:
450 data = _reader.read(block_size)
451 data = _reader.read(block_size)
451 if not data:
452 if not data:
452 break
453 break
453 yield data
454 yield data
454
455
455 response = Response(app_iter=archive_iterator(reader))
456 response = Response(app_iter=archive_iterator(reader))
456 response.content_disposition = f'attachment; filename={response_archive_name}'
457 response.content_disposition = f'attachment; filename={response_archive_name}'
457 response.content_type = str(content_type)
458 response.content_type = str(content_type)
458
459
459 try:
460 try:
460 return response
461 return response
461 finally:
462 finally:
462 # store download action
463 # store download action
463 audit_logger.store_web(
464 audit_logger.store_web(
464 'repo.archive.download', action_data={
465 'repo.archive.download', action_data={
465 'user_agent': self.request.user_agent,
466 'user_agent': self.request.user_agent,
466 'archive_name': archive_name_key,
467 'archive_name': archive_name_key,
467 'archive_spec': fname,
468 'archive_spec': fname,
468 'archive_cached': use_cached_archive},
469 'archive_cached': use_cached_archive},
469 user=self._rhodecode_user,
470 user=self._rhodecode_user,
470 repo=self.db_repo,
471 repo=self.db_repo,
471 commit=True
472 commit=True
472 )
473 )
473
474
474 def _get_file_node(self, commit_id, f_path):
475 def _get_file_node(self, commit_id, f_path):
475 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
476 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
477 try:
478 try:
478 node = commit.get_node(f_path)
479 node = commit.get_node(f_path)
479 if node.is_dir():
480 if node.is_dir():
480 raise NodeError(f'{node} path is a {type(node)} not a file')
481 raise NodeError(f'{node} path is a {type(node)} not a file')
481 except NodeDoesNotExistError:
482 except NodeDoesNotExistError:
482 commit = EmptyCommit(
483 commit = EmptyCommit(
483 commit_id=commit_id,
484 commit_id=commit_id,
484 idx=commit.idx,
485 idx=commit.idx,
485 repo=commit.repository,
486 repo=commit.repository,
486 alias=commit.repository.alias,
487 alias=commit.repository.alias,
487 message=commit.message,
488 message=commit.message,
488 author=commit.author,
489 author=commit.author,
489 date=commit.date)
490 date=commit.date)
490 node = FileNode(safe_bytes(f_path), b'', commit=commit)
491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
491 else:
492 else:
492 commit = EmptyCommit(
493 commit = EmptyCommit(
493 repo=self.rhodecode_vcs_repo,
494 repo=self.rhodecode_vcs_repo,
494 alias=self.rhodecode_vcs_repo.alias)
495 alias=self.rhodecode_vcs_repo.alias)
495 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 return node
497 return node
497
498
498 @LoginRequired()
499 @LoginRequired()
499 @HasRepoPermissionAnyDecorator(
500 @HasRepoPermissionAnyDecorator(
500 'repository.read', 'repository.write', 'repository.admin')
501 'repository.read', 'repository.write', 'repository.admin')
501 def repo_files_diff(self):
502 def repo_files_diff(self):
502 c = self.load_default_context()
503 c = self.load_default_context()
503 f_path = self._get_f_path(self.request.matchdict)
504 f_path = self._get_f_path(self.request.matchdict)
504 diff1 = self.request.GET.get('diff1', '')
505 diff1 = self.request.GET.get('diff1', '')
505 diff2 = self.request.GET.get('diff2', '')
506 diff2 = self.request.GET.get('diff2', '')
506
507
507 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
508
509
509 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
510 line_context = self.request.GET.get('context', 3)
511 line_context = self.request.GET.get('context', 3)
511
512
512 if not any((diff1, diff2)):
513 if not any((diff1, diff2)):
513 h.flash(
514 h.flash(
514 'Need query parameter "diff1" or "diff2" to generate a diff.',
515 'Need query parameter "diff1" or "diff2" to generate a diff.',
515 category='error')
516 category='error')
516 raise HTTPBadRequest()
517 raise HTTPBadRequest()
517
518
518 c.action = self.request.GET.get('diff')
519 c.action = self.request.GET.get('diff')
519 if c.action not in ['download', 'raw']:
520 if c.action not in ['download', 'raw']:
520 compare_url = h.route_path(
521 compare_url = h.route_path(
521 'repo_compare',
522 'repo_compare',
522 repo_name=self.db_repo_name,
523 repo_name=self.db_repo_name,
523 source_ref_type='rev',
524 source_ref_type='rev',
524 source_ref=diff1,
525 source_ref=diff1,
525 target_repo=self.db_repo_name,
526 target_repo=self.db_repo_name,
526 target_ref_type='rev',
527 target_ref_type='rev',
527 target_ref=diff2,
528 target_ref=diff2,
528 _query=dict(f_path=f_path))
529 _query=dict(f_path=f_path))
529 # redirect to new view if we render diff
530 # redirect to new view if we render diff
530 raise HTTPFound(compare_url)
531 raise HTTPFound(compare_url)
531
532
532 try:
533 try:
533 node1 = self._get_file_node(diff1, path1)
534 node1 = self._get_file_node(diff1, path1)
534 node2 = self._get_file_node(diff2, f_path)
535 node2 = self._get_file_node(diff2, f_path)
535 except (RepositoryError, NodeError):
536 except (RepositoryError, NodeError):
536 log.exception("Exception while trying to get node from repository")
537 log.exception("Exception while trying to get node from repository")
537 raise HTTPFound(
538 raise HTTPFound(
538 h.route_path('repo_files', repo_name=self.db_repo_name,
539 h.route_path('repo_files', repo_name=self.db_repo_name,
539 commit_id='tip', f_path=f_path))
540 commit_id='tip', f_path=f_path))
540
541
541 if all(isinstance(node.commit, EmptyCommit)
542 if all(isinstance(node.commit, EmptyCommit)
542 for node in (node1, node2)):
543 for node in (node1, node2)):
543 raise HTTPNotFound()
544 raise HTTPNotFound()
544
545
545 c.commit_1 = node1.commit
546 c.commit_1 = node1.commit
546 c.commit_2 = node2.commit
547 c.commit_2 = node2.commit
547
548
548 if c.action == 'download':
549 if c.action == 'download':
549 _diff = diffs.get_gitdiff(node1, node2,
550 _diff = diffs.get_gitdiff(node1, node2,
550 ignore_whitespace=ignore_whitespace,
551 ignore_whitespace=ignore_whitespace,
551 context=line_context)
552 context=line_context)
552 # NOTE: this was using diff_format='gitdiff'
553 # NOTE: this was using diff_format='gitdiff'
553 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
554
555
555 response = Response(self.path_filter.get_raw_patch(diff))
556 response = Response(self.path_filter.get_raw_patch(diff))
556 response.content_type = 'text/plain'
557 response.content_type = 'text/plain'
557 response.content_disposition = (
558 response.content_disposition = (
558 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
559 )
560 )
560 charset = self._get_default_encoding(c)
561 charset = self._get_default_encoding(c)
561 if charset:
562 if charset:
562 response.charset = charset
563 response.charset = charset
563 return response
564 return response
564
565
565 elif c.action == 'raw':
566 elif c.action == 'raw':
566 _diff = diffs.get_gitdiff(node1, node2,
567 _diff = diffs.get_gitdiff(node1, node2,
567 ignore_whitespace=ignore_whitespace,
568 ignore_whitespace=ignore_whitespace,
568 context=line_context)
569 context=line_context)
569 # NOTE: this was using diff_format='gitdiff'
570 # NOTE: this was using diff_format='gitdiff'
570 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
571
572
572 response = Response(self.path_filter.get_raw_patch(diff))
573 response = Response(self.path_filter.get_raw_patch(diff))
573 response.content_type = 'text/plain'
574 response.content_type = 'text/plain'
574 charset = self._get_default_encoding(c)
575 charset = self._get_default_encoding(c)
575 if charset:
576 if charset:
576 response.charset = charset
577 response.charset = charset
577 return response
578 return response
578
579
579 # in case we ever end up here
580 # in case we ever end up here
580 raise HTTPNotFound()
581 raise HTTPNotFound()
581
582
582 @LoginRequired()
583 @LoginRequired()
583 @HasRepoPermissionAnyDecorator(
584 @HasRepoPermissionAnyDecorator(
584 'repository.read', 'repository.write', 'repository.admin')
585 'repository.read', 'repository.write', 'repository.admin')
585 def repo_files_diff_2way_redirect(self):
586 def repo_files_diff_2way_redirect(self):
586 """
587 """
587 Kept only to make OLD links work
588 Kept only to make OLD links work
588 """
589 """
589 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 diff1 = self.request.GET.get('diff1', '')
591 diff1 = self.request.GET.get('diff1', '')
591 diff2 = self.request.GET.get('diff2', '')
592 diff2 = self.request.GET.get('diff2', '')
592
593
593 if not any((diff1, diff2)):
594 if not any((diff1, diff2)):
594 h.flash(
595 h.flash(
595 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 category='error')
597 category='error')
597 raise HTTPBadRequest()
598 raise HTTPBadRequest()
598
599
599 compare_url = h.route_path(
600 compare_url = h.route_path(
600 'repo_compare',
601 'repo_compare',
601 repo_name=self.db_repo_name,
602 repo_name=self.db_repo_name,
602 source_ref_type='rev',
603 source_ref_type='rev',
603 source_ref=diff1,
604 source_ref=diff1,
604 target_ref_type='rev',
605 target_ref_type='rev',
605 target_ref=diff2,
606 target_ref=diff2,
606 _query=dict(f_path=f_path, diffmode='sideside',
607 _query=dict(f_path=f_path, diffmode='sideside',
607 target_repo=self.db_repo_name,))
608 target_repo=self.db_repo_name,))
608 raise HTTPFound(compare_url)
609 raise HTTPFound(compare_url)
609
610
610 @LoginRequired()
611 @LoginRequired()
611 def repo_files_default_commit_redirect(self):
612 def repo_files_default_commit_redirect(self):
612 """
613 """
613 Special page that redirects to the landing page of files based on the default
614 Special page that redirects to the landing page of files based on the default
614 commit for repository
615 commit for repository
615 """
616 """
616 c = self.load_default_context()
617 c = self.load_default_context()
617 ref_name = c.rhodecode_db_repo.landing_ref_name
618 ref_name = c.rhodecode_db_repo.landing_ref_name
618 landing_url = h.repo_files_by_ref_url(
619 landing_url = h.repo_files_by_ref_url(
619 c.rhodecode_db_repo.repo_name,
620 c.rhodecode_db_repo.repo_name,
620 c.rhodecode_db_repo.repo_type,
621 c.rhodecode_db_repo.repo_type,
621 f_path='',
622 f_path='',
622 ref_name=ref_name,
623 ref_name=ref_name,
623 commit_id='tip',
624 commit_id='tip',
624 query=dict(at=ref_name)
625 query=dict(at=ref_name)
625 )
626 )
626
627
627 raise HTTPFound(landing_url)
628 raise HTTPFound(landing_url)
628
629
629 @LoginRequired()
630 @LoginRequired()
630 @HasRepoPermissionAnyDecorator(
631 @HasRepoPermissionAnyDecorator(
631 'repository.read', 'repository.write', 'repository.admin')
632 'repository.read', 'repository.write', 'repository.admin')
632 def repo_files(self):
633 def repo_files(self):
633 c = self.load_default_context()
634 c = self.load_default_context()
634
635
635 view_name = getattr(self.request.matched_route, 'name', None)
636 view_name = getattr(self.request.matched_route, 'name', None)
636
637
637 c.annotate = view_name == 'repo_files:annotated'
638 c.annotate = view_name == 'repo_files:annotated'
638 # default is false, but .rst/.md files later are auto rendered, we can
639 # default is false, but .rst/.md files later are auto rendered, we can
639 # overwrite auto rendering by setting this GET flag
640 # overwrite auto rendering by setting this GET flag
640 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
641
642
642 commit_id, f_path = self._get_commit_and_path()
643 commit_id, f_path = self._get_commit_and_path()
643
644
644 c.commit = self._get_commit_or_redirect(commit_id)
645 c.commit = self._get_commit_or_redirect(commit_id)
645 c.branch = self.request.GET.get('branch', None)
646 c.branch = self.request.GET.get('branch', None)
646 c.f_path = f_path
647 c.f_path = f_path
647 at_rev = self.request.GET.get('at')
648 at_rev = self.request.GET.get('at')
648
649
649 # prev link
650 # prev link
650 try:
651 try:
651 prev_commit = c.commit.prev(c.branch)
652 prev_commit = c.commit.prev(c.branch)
652 c.prev_commit = prev_commit
653 c.prev_commit = prev_commit
653 c.url_prev = h.route_path(
654 c.url_prev = h.route_path(
654 'repo_files', repo_name=self.db_repo_name,
655 'repo_files', repo_name=self.db_repo_name,
655 commit_id=prev_commit.raw_id, f_path=f_path)
656 commit_id=prev_commit.raw_id, f_path=f_path)
656 if c.branch:
657 if c.branch:
657 c.url_prev += '?branch=%s' % c.branch
658 c.url_prev += '?branch=%s' % c.branch
658 except (CommitDoesNotExistError, VCSError):
659 except (CommitDoesNotExistError, VCSError):
659 c.url_prev = '#'
660 c.url_prev = '#'
660 c.prev_commit = EmptyCommit()
661 c.prev_commit = EmptyCommit()
661
662
662 # next link
663 # next link
663 try:
664 try:
664 next_commit = c.commit.next(c.branch)
665 next_commit = c.commit.next(c.branch)
665 c.next_commit = next_commit
666 c.next_commit = next_commit
666 c.url_next = h.route_path(
667 c.url_next = h.route_path(
667 'repo_files', repo_name=self.db_repo_name,
668 'repo_files', repo_name=self.db_repo_name,
668 commit_id=next_commit.raw_id, f_path=f_path)
669 commit_id=next_commit.raw_id, f_path=f_path)
669 if c.branch:
670 if c.branch:
670 c.url_next += '?branch=%s' % c.branch
671 c.url_next += '?branch=%s' % c.branch
671 except (CommitDoesNotExistError, VCSError):
672 except (CommitDoesNotExistError, VCSError):
672 c.url_next = '#'
673 c.url_next = '#'
673 c.next_commit = EmptyCommit()
674 c.next_commit = EmptyCommit()
674
675
675 # files or dirs
676 # files or dirs
676 try:
677 try:
677 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
678 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
678
679
679 c.file_author = True
680 c.file_author = True
680 c.file_tree = ''
681 c.file_tree = ''
681
682
682 # load file content
683 # load file content
683 if c.file.is_file():
684 if c.file.is_file():
684 c.lf_node = {}
685 c.lf_node = {}
685
686
686 has_lf_enabled = self._is_lf_enabled(self.db_repo)
687 has_lf_enabled = self._is_lf_enabled(self.db_repo)
687 if has_lf_enabled:
688 if has_lf_enabled:
688 c.lf_node = c.file.get_largefile_node()
689 c.lf_node = c.file.get_largefile_node()
689
690
690 c.file_source_page = 'true'
691 c.file_source_page = 'true'
691 c.file_last_commit = c.file.last_commit
692 c.file_last_commit = c.file.last_commit
692
693
693 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
694 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
694
695
695 if not (c.file_size_too_big or c.file.is_binary):
696 if not (c.file_size_too_big or c.file.is_binary):
696 if c.annotate: # annotation has precedence over renderer
697 if c.annotate: # annotation has precedence over renderer
697 c.annotated_lines = filenode_as_annotated_lines_tokens(
698 c.annotated_lines = filenode_as_annotated_lines_tokens(
698 c.file
699 c.file
699 )
700 )
700 else:
701 else:
701 c.renderer = (
702 c.renderer = (
702 c.renderer and h.renderer_from_filename(c.file.path)
703 c.renderer and h.renderer_from_filename(c.file.path)
703 )
704 )
704 if not c.renderer:
705 if not c.renderer:
705 c.lines = filenode_as_lines_tokens(c.file)
706 c.lines = filenode_as_lines_tokens(c.file)
706
707
707 _branch_name, _sha_commit_id, is_head = \
708 _branch_name, _sha_commit_id, is_head = \
708 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
709 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
709 landing_ref=self.db_repo.landing_ref_name)
710 landing_ref=self.db_repo.landing_ref_name)
710 c.on_branch_head = is_head
711 c.on_branch_head = is_head
711
712
712 branch = c.commit.branch if (
713 branch = c.commit.branch if (
713 c.commit.branch and '/' not in c.commit.branch) else None
714 c.commit.branch and '/' not in c.commit.branch) else None
714 c.branch_or_raw_id = branch or c.commit.raw_id
715 c.branch_or_raw_id = branch or c.commit.raw_id
715 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
716 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
716
717
717 author = c.file_last_commit.author
718 author = c.file_last_commit.author
718 c.authors = [[
719 c.authors = [[
719 h.email(author),
720 h.email(author),
720 h.person(author, 'username_or_name_or_email'),
721 h.person(author, 'username_or_name_or_email'),
721 1
722 1
722 ]]
723 ]]
723
724
724 else: # load tree content at path
725 else: # load tree content at path
725 c.file_source_page = 'false'
726 c.file_source_page = 'false'
726 c.authors = []
727 c.authors = []
727 # this loads a simple tree without metadata to speed things up
728 # this loads a simple tree without metadata to speed things up
728 # later via ajax we call repo_nodetree_full and fetch whole
729 # later via ajax we call repo_nodetree_full and fetch whole
729 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
730 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
730
731
731 c.readme_data, c.readme_file = \
732 c.readme_data, c.readme_file = \
732 self._get_readme_data(self.db_repo, c.visual.default_renderer,
733 self._get_readme_data(self.db_repo, c.visual.default_renderer,
733 c.commit.raw_id, f_path)
734 c.commit.raw_id, f_path)
734
735
735 except RepositoryError as e:
736 except RepositoryError as e:
736 h.flash(h.escape(safe_str(e)), category='error')
737 h.flash(h.escape(safe_str(e)), category='error')
737 raise HTTPNotFound()
738 raise HTTPNotFound()
738
739
739 if self.request.environ.get('HTTP_X_PJAX'):
740 if self.request.environ.get('HTTP_X_PJAX'):
740 html = render('rhodecode:templates/files/files_pjax.mako',
741 html = render('rhodecode:templates/files/files_pjax.mako',
741 self._get_template_context(c), self.request)
742 self._get_template_context(c), self.request)
742 else:
743 else:
743 html = render('rhodecode:templates/files/files.mako',
744 html = render('rhodecode:templates/files/files.mako',
744 self._get_template_context(c), self.request)
745 self._get_template_context(c), self.request)
745 return Response(html)
746 return Response(html)
746
747
747 @HasRepoPermissionAnyDecorator(
748 @HasRepoPermissionAnyDecorator(
748 'repository.read', 'repository.write', 'repository.admin')
749 'repository.read', 'repository.write', 'repository.admin')
749 def repo_files_annotated_previous(self):
750 def repo_files_annotated_previous(self):
750 self.load_default_context()
751 self.load_default_context()
751
752
752 commit_id, f_path = self._get_commit_and_path()
753 commit_id, f_path = self._get_commit_and_path()
753 commit = self._get_commit_or_redirect(commit_id)
754 commit = self._get_commit_or_redirect(commit_id)
754 prev_commit_id = commit.raw_id
755 prev_commit_id = commit.raw_id
755 line_anchor = self.request.GET.get('line_anchor')
756 line_anchor = self.request.GET.get('line_anchor')
756 is_file = False
757 is_file = False
757 try:
758 try:
758 _file = commit.get_node(f_path)
759 _file = commit.get_node(f_path)
759 is_file = _file.is_file()
760 is_file = _file.is_file()
760 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
761 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
761 pass
762 pass
762
763
763 if is_file:
764 if is_file:
764 history = commit.get_path_history(f_path)
765 history = commit.get_path_history(f_path)
765 prev_commit_id = history[1].raw_id \
766 prev_commit_id = history[1].raw_id \
766 if len(history) > 1 else prev_commit_id
767 if len(history) > 1 else prev_commit_id
767 prev_url = h.route_path(
768 prev_url = h.route_path(
768 'repo_files:annotated', repo_name=self.db_repo_name,
769 'repo_files:annotated', repo_name=self.db_repo_name,
769 commit_id=prev_commit_id, f_path=f_path,
770 commit_id=prev_commit_id, f_path=f_path,
770 _anchor=f'L{line_anchor}')
771 _anchor=f'L{line_anchor}')
771
772
772 raise HTTPFound(prev_url)
773 raise HTTPFound(prev_url)
773
774
774 @LoginRequired()
775 @LoginRequired()
775 @HasRepoPermissionAnyDecorator(
776 @HasRepoPermissionAnyDecorator(
776 'repository.read', 'repository.write', 'repository.admin')
777 'repository.read', 'repository.write', 'repository.admin')
777 def repo_nodetree_full(self):
778 def repo_nodetree_full(self):
778 """
779 """
779 Returns rendered html of file tree that contains commit date,
780 Returns rendered html of file tree that contains commit date,
780 author, commit_id for the specified combination of
781 author, commit_id for the specified combination of
781 repo, commit_id and file path
782 repo, commit_id and file path
782 """
783 """
783 c = self.load_default_context()
784 c = self.load_default_context()
784
785
785 commit_id, f_path = self._get_commit_and_path()
786 commit_id, f_path = self._get_commit_and_path()
786 commit = self._get_commit_or_redirect(commit_id)
787 commit = self._get_commit_or_redirect(commit_id)
787 try:
788 try:
788 dir_node = commit.get_node(f_path)
789 dir_node = commit.get_node(f_path)
789 except RepositoryError as e:
790 except RepositoryError as e:
790 return Response(f'error: {h.escape(safe_str(e))}')
791 return Response(f'error: {h.escape(safe_str(e))}')
791
792
792 if dir_node.is_file():
793 if dir_node.is_file():
793 return Response('')
794 return Response('')
794
795
795 c.file = dir_node
796 c.file = dir_node
796 c.commit = commit
797 c.commit = commit
797 at_rev = self.request.GET.get('at')
798 at_rev = self.request.GET.get('at')
798
799
799 html = self._get_tree_at_commit(
800 html = self._get_tree_at_commit(
800 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
801 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
801
802
802 return Response(html)
803 return Response(html)
803
804
804 def _get_attachement_headers(self, f_path):
805 def _get_attachement_headers(self, f_path):
805 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
806 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
806 safe_path = f_name.replace('"', '\\"')
807 safe_path = f_name.replace('"', '\\"')
807 encoded_path = urllib.parse.quote(f_name)
808 encoded_path = urllib.parse.quote(f_name)
808
809
809 return "attachment; " \
810 return "attachment; " \
810 "filename=\"{}\"; " \
811 "filename=\"{}\"; " \
811 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
812 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
812
813
813 @LoginRequired()
814 @LoginRequired()
814 @HasRepoPermissionAnyDecorator(
815 @HasRepoPermissionAnyDecorator(
815 'repository.read', 'repository.write', 'repository.admin')
816 'repository.read', 'repository.write', 'repository.admin')
816 def repo_file_raw(self):
817 def repo_file_raw(self):
817 """
818 """
818 Action for show as raw, some mimetypes are "rendered",
819 Action for show as raw, some mimetypes are "rendered",
819 those include images, icons.
820 those include images, icons.
820 """
821 """
821 c = self.load_default_context()
822 c = self.load_default_context()
822
823
823 commit_id, f_path = self._get_commit_and_path()
824 commit_id, f_path = self._get_commit_and_path()
824 commit = self._get_commit_or_redirect(commit_id)
825 commit = self._get_commit_or_redirect(commit_id)
825 file_node = self._get_filenode_or_redirect(commit, f_path)
826 file_node = self._get_filenode_or_redirect(commit, f_path)
826
827
827 raw_mimetype_mapping = {
828 raw_mimetype_mapping = {
828 # map original mimetype to a mimetype used for "show as raw"
829 # map original mimetype to a mimetype used for "show as raw"
829 # you can also provide a content-disposition to override the
830 # you can also provide a content-disposition to override the
830 # default "attachment" disposition.
831 # default "attachment" disposition.
831 # orig_type: (new_type, new_dispo)
832 # orig_type: (new_type, new_dispo)
832
833
833 # show images inline:
834 # show images inline:
834 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
835 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
835 # for example render an SVG with javascript inside or even render
836 # for example render an SVG with javascript inside or even render
836 # HTML.
837 # HTML.
837 'image/x-icon': ('image/x-icon', 'inline'),
838 'image/x-icon': ('image/x-icon', 'inline'),
838 'image/png': ('image/png', 'inline'),
839 'image/png': ('image/png', 'inline'),
839 'image/gif': ('image/gif', 'inline'),
840 'image/gif': ('image/gif', 'inline'),
840 'image/jpeg': ('image/jpeg', 'inline'),
841 'image/jpeg': ('image/jpeg', 'inline'),
841 'application/pdf': ('application/pdf', 'inline'),
842 'application/pdf': ('application/pdf', 'inline'),
842 }
843 }
843
844
844 mimetype = file_node.mimetype
845 mimetype = file_node.mimetype
845 try:
846 try:
846 mimetype, disposition = raw_mimetype_mapping[mimetype]
847 mimetype, disposition = raw_mimetype_mapping[mimetype]
847 except KeyError:
848 except KeyError:
848 # we don't know anything special about this, handle it safely
849 # we don't know anything special about this, handle it safely
849 if file_node.is_binary:
850 if file_node.is_binary:
850 # do same as download raw for binary files
851 # do same as download raw for binary files
851 mimetype, disposition = 'application/octet-stream', 'attachment'
852 mimetype, disposition = 'application/octet-stream', 'attachment'
852 else:
853 else:
853 # do not just use the original mimetype, but force text/plain,
854 # do not just use the original mimetype, but force text/plain,
854 # otherwise it would serve text/html and that might be unsafe.
855 # otherwise it would serve text/html and that might be unsafe.
855 # Note: underlying vcs library fakes text/plain mimetype if the
856 # Note: underlying vcs library fakes text/plain mimetype if the
856 # mimetype can not be determined and it thinks it is not
857 # mimetype can not be determined and it thinks it is not
857 # binary.This might lead to erroneous text display in some
858 # binary.This might lead to erroneous text display in some
858 # cases, but helps in other cases, like with text files
859 # cases, but helps in other cases, like with text files
859 # without extension.
860 # without extension.
860 mimetype, disposition = 'text/plain', 'inline'
861 mimetype, disposition = 'text/plain', 'inline'
861
862
862 if disposition == 'attachment':
863 if disposition == 'attachment':
863 disposition = self._get_attachement_headers(f_path)
864 disposition = self._get_attachement_headers(f_path)
864
865
865 stream_content = file_node.stream_bytes()
866 stream_content = file_node.stream_bytes()
866
867
867 response = Response(app_iter=stream_content)
868 response = Response(app_iter=stream_content)
868 response.content_disposition = disposition
869 response.content_disposition = disposition
869 response.content_type = mimetype
870 response.content_type = mimetype
870
871
871 charset = self._get_default_encoding(c)
872 charset = self._get_default_encoding(c)
872 if charset:
873 if charset:
873 response.charset = charset
874 response.charset = charset
874
875
875 return response
876 return response
876
877
877 @LoginRequired()
878 @LoginRequired()
878 @HasRepoPermissionAnyDecorator(
879 @HasRepoPermissionAnyDecorator(
879 'repository.read', 'repository.write', 'repository.admin')
880 'repository.read', 'repository.write', 'repository.admin')
880 def repo_file_download(self):
881 def repo_file_download(self):
881 c = self.load_default_context()
882 c = self.load_default_context()
882
883
883 commit_id, f_path = self._get_commit_and_path()
884 commit_id, f_path = self._get_commit_and_path()
884 commit = self._get_commit_or_redirect(commit_id)
885 commit = self._get_commit_or_redirect(commit_id)
885 file_node = self._get_filenode_or_redirect(commit, f_path)
886 file_node = self._get_filenode_or_redirect(commit, f_path)
886
887
887 if self.request.GET.get('lf'):
888 if self.request.GET.get('lf'):
888 # only if lf get flag is passed, we download this file
889 # only if lf get flag is passed, we download this file
889 # as LFS/Largefile
890 # as LFS/Largefile
890 lf_node = file_node.get_largefile_node()
891 lf_node = file_node.get_largefile_node()
891 if lf_node:
892 if lf_node:
892 # overwrite our pointer with the REAL large-file
893 # overwrite our pointer with the REAL large-file
893 file_node = lf_node
894 file_node = lf_node
894
895
895 disposition = self._get_attachement_headers(f_path)
896 disposition = self._get_attachement_headers(f_path)
896
897
897 stream_content = file_node.stream_bytes()
898 stream_content = file_node.stream_bytes()
898
899
899 response = Response(app_iter=stream_content)
900 response = Response(app_iter=stream_content)
900 response.content_disposition = disposition
901 response.content_disposition = disposition
901 response.content_type = file_node.mimetype
902 response.content_type = file_node.mimetype
902
903
903 charset = self._get_default_encoding(c)
904 charset = self._get_default_encoding(c)
904 if charset:
905 if charset:
905 response.charset = charset
906 response.charset = charset
906
907
907 return response
908 return response
908
909
909 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
910 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
910
911
911 cache_seconds = safe_int(
912 cache_seconds = safe_int(
912 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
913 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
913 cache_on = cache_seconds > 0
914 cache_on = cache_seconds > 0
914 log.debug(
915 log.debug(
915 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
916 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
916 'with caching: %s[TTL: %ss]' % (
917 'with caching: %s[TTL: %ss]' % (
917 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
918 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
918
919
919 cache_namespace_uid = f'repo.{repo_id}'
920 cache_namespace_uid = f'repo.{repo_id}'
920 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
921 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
921
922
922 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
923 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
923 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
924 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
924 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
925 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
925 _repo_id, commit_id, f_path)
926 _repo_id, commit_id, f_path)
926 try:
927 try:
927 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
928 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
928 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
929 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
929 log.exception(safe_str(e))
930 log.exception(safe_str(e))
930 h.flash(h.escape(safe_str(e)), category='error')
931 h.flash(h.escape(safe_str(e)), category='error')
931 raise HTTPFound(h.route_path(
932 raise HTTPFound(h.route_path(
932 'repo_files', repo_name=self.db_repo_name,
933 'repo_files', repo_name=self.db_repo_name,
933 commit_id='tip', f_path='/'))
934 commit_id='tip', f_path='/'))
934
935
935 return _d + _f
936 return _d + _f
936
937
937 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
938 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
938 commit_id, f_path)
939 commit_id, f_path)
939 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
940 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
940
941
941 @LoginRequired()
942 @LoginRequired()
942 @HasRepoPermissionAnyDecorator(
943 @HasRepoPermissionAnyDecorator(
943 'repository.read', 'repository.write', 'repository.admin')
944 'repository.read', 'repository.write', 'repository.admin')
944 def repo_nodelist(self):
945 def repo_nodelist(self):
945 self.load_default_context()
946 self.load_default_context()
946
947
947 commit_id, f_path = self._get_commit_and_path()
948 commit_id, f_path = self._get_commit_and_path()
948 commit = self._get_commit_or_redirect(commit_id)
949 commit = self._get_commit_or_redirect(commit_id)
949
950
950 metadata = self._get_nodelist_at_commit(
951 metadata = self._get_nodelist_at_commit(
951 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
952 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
952 return {'nodes': [x for x in metadata]}
953 return {'nodes': [x for x in metadata]}
953
954
954 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
955 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
955 items = []
956 items = []
956 for name, commit_id in branches_or_tags.items():
957 for name, commit_id in branches_or_tags.items():
957 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
958 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
958 items.append((sym_ref, name, ref_type))
959 items.append((sym_ref, name, ref_type))
959 return items
960 return items
960
961
961 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
962 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
962 return commit_id
963 return commit_id
963
964
964 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
965 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
965 return commit_id
966 return commit_id
966
967
967 # NOTE(dan): old code we used in "diff" mode compare
968 # NOTE(dan): old code we used in "diff" mode compare
968 new_f_path = vcspath.join(name, f_path)
969 new_f_path = vcspath.join(name, f_path)
969 return f'{new_f_path}@{commit_id}'
970 return f'{new_f_path}@{commit_id}'
970
971
971 def _get_node_history(self, commit_obj, f_path, commits=None):
972 def _get_node_history(self, commit_obj, f_path, commits=None):
972 """
973 """
973 get commit history for given node
974 get commit history for given node
974
975
975 :param commit_obj: commit to calculate history
976 :param commit_obj: commit to calculate history
976 :param f_path: path for node to calculate history for
977 :param f_path: path for node to calculate history for
977 :param commits: if passed don't calculate history and take
978 :param commits: if passed don't calculate history and take
978 commits defined in this list
979 commits defined in this list
979 """
980 """
980 _ = self.request.translate
981 _ = self.request.translate
981
982
982 # calculate history based on tip
983 # calculate history based on tip
983 tip = self.rhodecode_vcs_repo.get_commit()
984 tip = self.rhodecode_vcs_repo.get_commit()
984 if commits is None:
985 if commits is None:
985 pre_load = ["author", "branch"]
986 pre_load = ["author", "branch"]
986 try:
987 try:
987 commits = tip.get_path_history(f_path, pre_load=pre_load)
988 commits = tip.get_path_history(f_path, pre_load=pre_load)
988 except (NodeDoesNotExistError, CommitError):
989 except (NodeDoesNotExistError, CommitError):
989 # this node is not present at tip!
990 # this node is not present at tip!
990 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
991 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
991
992
992 history = []
993 history = []
993 commits_group = ([], _("Changesets"))
994 commits_group = ([], _("Changesets"))
994 for commit in commits:
995 for commit in commits:
995 branch = ' (%s)' % commit.branch if commit.branch else ''
996 branch = ' (%s)' % commit.branch if commit.branch else ''
996 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
997 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
997 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
998 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
998 history.append(commits_group)
999 history.append(commits_group)
999
1000
1000 symbolic_reference = self._symbolic_reference
1001 symbolic_reference = self._symbolic_reference
1001
1002
1002 if self.rhodecode_vcs_repo.alias == 'svn':
1003 if self.rhodecode_vcs_repo.alias == 'svn':
1003 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1004 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1004 f_path, self.rhodecode_vcs_repo)
1005 f_path, self.rhodecode_vcs_repo)
1005 if adjusted_f_path != f_path:
1006 if adjusted_f_path != f_path:
1006 log.debug(
1007 log.debug(
1007 'Recognized svn tag or branch in file "%s", using svn '
1008 'Recognized svn tag or branch in file "%s", using svn '
1008 'specific symbolic references', f_path)
1009 'specific symbolic references', f_path)
1009 f_path = adjusted_f_path
1010 f_path = adjusted_f_path
1010 symbolic_reference = self._symbolic_reference_svn
1011 symbolic_reference = self._symbolic_reference_svn
1011
1012
1012 branches = self._create_references(
1013 branches = self._create_references(
1013 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1014 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1014 branches_group = (branches, _("Branches"))
1015 branches_group = (branches, _("Branches"))
1015
1016
1016 tags = self._create_references(
1017 tags = self._create_references(
1017 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1018 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1018 tags_group = (tags, _("Tags"))
1019 tags_group = (tags, _("Tags"))
1019
1020
1020 history.append(branches_group)
1021 history.append(branches_group)
1021 history.append(tags_group)
1022 history.append(tags_group)
1022
1023
1023 return history, commits
1024 return history, commits
1024
1025
1025 @LoginRequired()
1026 @LoginRequired()
1026 @HasRepoPermissionAnyDecorator(
1027 @HasRepoPermissionAnyDecorator(
1027 'repository.read', 'repository.write', 'repository.admin')
1028 'repository.read', 'repository.write', 'repository.admin')
1028 def repo_file_history(self):
1029 def repo_file_history(self):
1029 self.load_default_context()
1030 self.load_default_context()
1030
1031
1031 commit_id, f_path = self._get_commit_and_path()
1032 commit_id, f_path = self._get_commit_and_path()
1032 commit = self._get_commit_or_redirect(commit_id)
1033 commit = self._get_commit_or_redirect(commit_id)
1033 file_node = self._get_filenode_or_redirect(commit, f_path)
1034 file_node = self._get_filenode_or_redirect(commit, f_path)
1034
1035
1035 if file_node.is_file():
1036 if file_node.is_file():
1036 file_history, _hist = self._get_node_history(commit, f_path)
1037 file_history, _hist = self._get_node_history(commit, f_path)
1037
1038
1038 res = []
1039 res = []
1039 for section_items, section in file_history:
1040 for section_items, section in file_history:
1040 items = []
1041 items = []
1041 for obj_id, obj_text, obj_type in section_items:
1042 for obj_id, obj_text, obj_type in section_items:
1042 at_rev = ''
1043 at_rev = ''
1043 if obj_type in ['branch', 'bookmark', 'tag']:
1044 if obj_type in ['branch', 'bookmark', 'tag']:
1044 at_rev = obj_text
1045 at_rev = obj_text
1045 entry = {
1046 entry = {
1046 'id': obj_id,
1047 'id': obj_id,
1047 'text': obj_text,
1048 'text': obj_text,
1048 'type': obj_type,
1049 'type': obj_type,
1049 'at_rev': at_rev
1050 'at_rev': at_rev
1050 }
1051 }
1051
1052
1052 items.append(entry)
1053 items.append(entry)
1053
1054
1054 res.append({
1055 res.append({
1055 'text': section,
1056 'text': section,
1056 'children': items
1057 'children': items
1057 })
1058 })
1058
1059
1059 data = {
1060 data = {
1060 'more': False,
1061 'more': False,
1061 'results': res
1062 'results': res
1062 }
1063 }
1063 return data
1064 return data
1064
1065
1065 log.warning('Cannot fetch history for directory')
1066 log.warning('Cannot fetch history for directory')
1066 raise HTTPBadRequest()
1067 raise HTTPBadRequest()
1067
1068
1068 @LoginRequired()
1069 @LoginRequired()
1069 @HasRepoPermissionAnyDecorator(
1070 @HasRepoPermissionAnyDecorator(
1070 'repository.read', 'repository.write', 'repository.admin')
1071 'repository.read', 'repository.write', 'repository.admin')
1071 def repo_file_authors(self):
1072 def repo_file_authors(self):
1072 c = self.load_default_context()
1073 c = self.load_default_context()
1073
1074
1074 commit_id, f_path = self._get_commit_and_path()
1075 commit_id, f_path = self._get_commit_and_path()
1075 commit = self._get_commit_or_redirect(commit_id)
1076 commit = self._get_commit_or_redirect(commit_id)
1076 file_node = self._get_filenode_or_redirect(commit, f_path)
1077 file_node = self._get_filenode_or_redirect(commit, f_path)
1077
1078
1078 if not file_node.is_file():
1079 if not file_node.is_file():
1079 raise HTTPBadRequest()
1080 raise HTTPBadRequest()
1080
1081
1081 c.file_last_commit = file_node.last_commit
1082 c.file_last_commit = file_node.last_commit
1082 if self.request.GET.get('annotate') == '1':
1083 if self.request.GET.get('annotate') == '1':
1083 # use _hist from annotation if annotation mode is on
1084 # use _hist from annotation if annotation mode is on
1084 commit_ids = {x[1] for x in file_node.annotate}
1085 commit_ids = {x[1] for x in file_node.annotate}
1085 _hist = (
1086 _hist = (
1086 self.rhodecode_vcs_repo.get_commit(commit_id)
1087 self.rhodecode_vcs_repo.get_commit(commit_id)
1087 for commit_id in commit_ids)
1088 for commit_id in commit_ids)
1088 else:
1089 else:
1089 _f_history, _hist = self._get_node_history(commit, f_path)
1090 _f_history, _hist = self._get_node_history(commit, f_path)
1090 c.file_author = False
1091 c.file_author = False
1091
1092
1092 unique = collections.OrderedDict()
1093 unique = collections.OrderedDict()
1093 for commit in _hist:
1094 for commit in _hist:
1094 author = commit.author
1095 author = commit.author
1095 if author not in unique:
1096 if author not in unique:
1096 unique[commit.author] = [
1097 unique[commit.author] = [
1097 h.email(author),
1098 h.email(author),
1098 h.person(author, 'username_or_name_or_email'),
1099 h.person(author, 'username_or_name_or_email'),
1099 1 # counter
1100 1 # counter
1100 ]
1101 ]
1101
1102
1102 else:
1103 else:
1103 # increase counter
1104 # increase counter
1104 unique[commit.author][2] += 1
1105 unique[commit.author][2] += 1
1105
1106
1106 c.authors = [val for val in unique.values()]
1107 c.authors = [val for val in unique.values()]
1107
1108
1108 return self._get_template_context(c)
1109 return self._get_template_context(c)
1109
1110
1110 @LoginRequired()
1111 @LoginRequired()
1111 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1112 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1112 def repo_files_check_head(self):
1113 def repo_files_check_head(self):
1113 self.load_default_context()
1114 self.load_default_context()
1114
1115
1115 commit_id, f_path = self._get_commit_and_path()
1116 commit_id, f_path = self._get_commit_and_path()
1116 _branch_name, _sha_commit_id, is_head = \
1117 _branch_name, _sha_commit_id, is_head = \
1117 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1118 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1118 landing_ref=self.db_repo.landing_ref_name)
1119 landing_ref=self.db_repo.landing_ref_name)
1119
1120
1120 new_path = self.request.POST.get('path')
1121 new_path = self.request.POST.get('path')
1121 operation = self.request.POST.get('operation')
1122 operation = self.request.POST.get('operation')
1122 path_exist = ''
1123 path_exist = ''
1123
1124
1124 if new_path and operation in ['create', 'upload']:
1125 if new_path and operation in ['create', 'upload']:
1125 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1126 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1126 try:
1127 try:
1127 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1128 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1128 # NOTE(dan): construct whole path without leading /
1129 # NOTE(dan): construct whole path without leading /
1129 file_node = commit_obj.get_node(new_f_path)
1130 file_node = commit_obj.get_node(new_f_path)
1130 if file_node is not None:
1131 if file_node is not None:
1131 path_exist = new_f_path
1132 path_exist = new_f_path
1132 except EmptyRepositoryError:
1133 except EmptyRepositoryError:
1133 pass
1134 pass
1134 except Exception:
1135 except Exception:
1135 pass
1136 pass
1136
1137
1137 return {
1138 return {
1138 'branch': _branch_name,
1139 'branch': _branch_name,
1139 'sha': _sha_commit_id,
1140 'sha': _sha_commit_id,
1140 'is_head': is_head,
1141 'is_head': is_head,
1141 'path_exists': path_exist
1142 'path_exists': path_exist
1142 }
1143 }
1143
1144
1144 @LoginRequired()
1145 @LoginRequired()
1145 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 def repo_files_remove_file(self):
1147 def repo_files_remove_file(self):
1147 _ = self.request.translate
1148 _ = self.request.translate
1148 c = self.load_default_context()
1149 c = self.load_default_context()
1149 commit_id, f_path = self._get_commit_and_path()
1150 commit_id, f_path = self._get_commit_and_path()
1150
1151
1151 self._ensure_not_locked()
1152 self._ensure_not_locked()
1152 _branch_name, _sha_commit_id, is_head = \
1153 _branch_name, _sha_commit_id, is_head = \
1153 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1154 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1154 landing_ref=self.db_repo.landing_ref_name)
1155 landing_ref=self.db_repo.landing_ref_name)
1155
1156
1156 self.forbid_non_head(is_head, f_path)
1157 self.forbid_non_head(is_head, f_path)
1157 self.check_branch_permission(_branch_name)
1158 self.check_branch_permission(_branch_name)
1158
1159
1159 c.commit = self._get_commit_or_redirect(commit_id)
1160 c.commit = self._get_commit_or_redirect(commit_id)
1160 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1161 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1161
1162
1162 c.default_message = _(
1163 c.default_message = _(
1163 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1164 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1164 c.f_path = f_path
1165 c.f_path = f_path
1165
1166
1166 return self._get_template_context(c)
1167 return self._get_template_context(c)
1167
1168
1168 @LoginRequired()
1169 @LoginRequired()
1169 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 @CSRFRequired()
1171 @CSRFRequired()
1171 def repo_files_delete_file(self):
1172 def repo_files_delete_file(self):
1172 _ = self.request.translate
1173 _ = self.request.translate
1173
1174
1174 c = self.load_default_context()
1175 c = self.load_default_context()
1175 commit_id, f_path = self._get_commit_and_path()
1176 commit_id, f_path = self._get_commit_and_path()
1176
1177
1177 self._ensure_not_locked()
1178 self._ensure_not_locked()
1178 _branch_name, _sha_commit_id, is_head = \
1179 _branch_name, _sha_commit_id, is_head = \
1179 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1180 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1180 landing_ref=self.db_repo.landing_ref_name)
1181 landing_ref=self.db_repo.landing_ref_name)
1181
1182
1182 self.forbid_non_head(is_head, f_path)
1183 self.forbid_non_head(is_head, f_path)
1183 self.check_branch_permission(_branch_name)
1184 self.check_branch_permission(_branch_name)
1184
1185
1185 c.commit = self._get_commit_or_redirect(commit_id)
1186 c.commit = self._get_commit_or_redirect(commit_id)
1186 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1187 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1187
1188
1188 c.default_message = _(
1189 c.default_message = _(
1189 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1190 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1190 c.f_path = f_path
1191 c.f_path = f_path
1191 node_path = f_path
1192 node_path = f_path
1192 author = self._rhodecode_db_user.full_contact
1193 author = self._rhodecode_db_user.full_contact
1193 message = self.request.POST.get('message') or c.default_message
1194 message = self.request.POST.get('message') or c.default_message
1194 try:
1195 try:
1195 nodes = {
1196 nodes = {
1196 safe_bytes(node_path): {
1197 safe_bytes(node_path): {
1197 'content': b''
1198 'content': b''
1198 }
1199 }
1199 }
1200 }
1200 ScmModel().delete_nodes(
1201 ScmModel().delete_nodes(
1201 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1202 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1202 message=message,
1203 message=message,
1203 nodes=nodes,
1204 nodes=nodes,
1204 parent_commit=c.commit,
1205 parent_commit=c.commit,
1205 author=author,
1206 author=author,
1206 )
1207 )
1207
1208
1208 h.flash(
1209 h.flash(
1209 _('Successfully deleted file `{}`').format(
1210 _('Successfully deleted file `{}`').format(
1210 h.escape(f_path)), category='success')
1211 h.escape(f_path)), category='success')
1211 except Exception:
1212 except Exception:
1212 log.exception('Error during commit operation')
1213 log.exception('Error during commit operation')
1213 h.flash(_('Error occurred during commit'), category='error')
1214 h.flash(_('Error occurred during commit'), category='error')
1214 raise HTTPFound(
1215 raise HTTPFound(
1215 h.route_path('repo_commit', repo_name=self.db_repo_name,
1216 h.route_path('repo_commit', repo_name=self.db_repo_name,
1216 commit_id='tip'))
1217 commit_id='tip'))
1217
1218
1218 @LoginRequired()
1219 @LoginRequired()
1219 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1220 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1220 def repo_files_edit_file(self):
1221 def repo_files_edit_file(self):
1221 _ = self.request.translate
1222 _ = self.request.translate
1222 c = self.load_default_context()
1223 c = self.load_default_context()
1223 commit_id, f_path = self._get_commit_and_path()
1224 commit_id, f_path = self._get_commit_and_path()
1224
1225
1225 self._ensure_not_locked()
1226 self._ensure_not_locked()
1226 _branch_name, _sha_commit_id, is_head = \
1227 _branch_name, _sha_commit_id, is_head = \
1227 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1228 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1228 landing_ref=self.db_repo.landing_ref_name)
1229 landing_ref=self.db_repo.landing_ref_name)
1229
1230
1230 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232
1233
1233 c.commit = self._get_commit_or_redirect(commit_id)
1234 c.commit = self._get_commit_or_redirect(commit_id)
1234 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1235 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1235
1236
1236 if c.file.is_binary:
1237 if c.file.is_binary:
1237 files_url = h.route_path(
1238 files_url = h.route_path(
1238 'repo_files',
1239 'repo_files',
1239 repo_name=self.db_repo_name,
1240 repo_name=self.db_repo_name,
1240 commit_id=c.commit.raw_id, f_path=f_path)
1241 commit_id=c.commit.raw_id, f_path=f_path)
1241 raise HTTPFound(files_url)
1242 raise HTTPFound(files_url)
1242
1243
1243 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1244 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1244 c.f_path = f_path
1245 c.f_path = f_path
1245
1246
1246 return self._get_template_context(c)
1247 return self._get_template_context(c)
1247
1248
1248 @LoginRequired()
1249 @LoginRequired()
1249 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1250 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1250 @CSRFRequired()
1251 @CSRFRequired()
1251 def repo_files_update_file(self):
1252 def repo_files_update_file(self):
1252 _ = self.request.translate
1253 _ = self.request.translate
1253 c = self.load_default_context()
1254 c = self.load_default_context()
1254 commit_id, f_path = self._get_commit_and_path()
1255 commit_id, f_path = self._get_commit_and_path()
1255
1256
1256 self._ensure_not_locked()
1257 self._ensure_not_locked()
1257
1258
1258 c.commit = self._get_commit_or_redirect(commit_id)
1259 c.commit = self._get_commit_or_redirect(commit_id)
1259 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1260 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1260
1261
1261 if c.file.is_binary:
1262 if c.file.is_binary:
1262 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1263 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1263 commit_id=c.commit.raw_id, f_path=f_path))
1264 commit_id=c.commit.raw_id, f_path=f_path))
1264
1265
1265 _branch_name, _sha_commit_id, is_head = \
1266 _branch_name, _sha_commit_id, is_head = \
1266 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1267 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1267 landing_ref=self.db_repo.landing_ref_name)
1268 landing_ref=self.db_repo.landing_ref_name)
1268
1269
1269 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1270 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1270 self.check_branch_permission(_branch_name, commit_id=commit_id)
1271 self.check_branch_permission(_branch_name, commit_id=commit_id)
1271
1272
1272 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1273 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1273 c.f_path = f_path
1274 c.f_path = f_path
1274
1275
1275 old_content = c.file.str_content
1276 old_content = c.file.str_content
1276 sl = old_content.splitlines(1)
1277 sl = old_content.splitlines(1)
1277 first_line = sl[0] if sl else ''
1278 first_line = sl[0] if sl else ''
1278
1279
1279 r_post = self.request.POST
1280 r_post = self.request.POST
1280 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1281 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1281 line_ending_mode = detect_mode(first_line, 0)
1282 line_ending_mode = detect_mode(first_line, 0)
1282 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1283 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1283
1284
1284 message = r_post.get('message') or c.default_message
1285 message = r_post.get('message') or c.default_message
1285
1286
1286 org_node_path = c.file.str_path
1287 org_node_path = c.file.str_path
1287 filename = r_post['filename']
1288 filename = r_post['filename']
1288
1289
1289 root_path = c.file.dir_path
1290 root_path = c.file.dir_path
1290 pure_path = self.create_pure_path(root_path, filename)
1291 pure_path = self.create_pure_path(root_path, filename)
1291 node_path = pure_path.as_posix()
1292 node_path = pure_path.as_posix()
1292
1293
1293 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1294 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1294 commit_id=commit_id)
1295 commit_id=commit_id)
1295 if content == old_content and node_path == org_node_path:
1296 if content == old_content and node_path == org_node_path:
1296 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1297 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1297 category='warning')
1298 category='warning')
1298 raise HTTPFound(default_redirect_url)
1299 raise HTTPFound(default_redirect_url)
1299
1300
1300 try:
1301 try:
1301 mapping = {
1302 mapping = {
1302 c.file.bytes_path: {
1303 c.file.bytes_path: {
1303 'org_filename': org_node_path,
1304 'org_filename': org_node_path,
1304 'filename': safe_bytes(node_path),
1305 'filename': safe_bytes(node_path),
1305 'content': safe_bytes(content),
1306 'content': safe_bytes(content),
1306 'lexer': '',
1307 'lexer': '',
1307 'op': 'mod',
1308 'op': 'mod',
1308 'mode': c.file.mode
1309 'mode': c.file.mode
1309 }
1310 }
1310 }
1311 }
1311
1312
1312 commit = ScmModel().update_nodes(
1313 commit = ScmModel().update_nodes(
1313 user=self._rhodecode_db_user.user_id,
1314 user=self._rhodecode_db_user.user_id,
1314 repo=self.db_repo,
1315 repo=self.db_repo,
1315 message=message,
1316 message=message,
1316 nodes=mapping,
1317 nodes=mapping,
1317 parent_commit=c.commit,
1318 parent_commit=c.commit,
1318 )
1319 )
1319
1320
1320 h.flash(_('Successfully committed changes to file `{}`').format(
1321 h.flash(_('Successfully committed changes to file `{}`').format(
1321 h.escape(f_path)), category='success')
1322 h.escape(f_path)), category='success')
1322 default_redirect_url = h.route_path(
1323 default_redirect_url = h.route_path(
1323 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1324 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1324
1325
1325 except Exception:
1326 except Exception:
1326 log.exception('Error occurred during commit')
1327 log.exception('Error occurred during commit')
1327 h.flash(_('Error occurred during commit'), category='error')
1328 h.flash(_('Error occurred during commit'), category='error')
1328
1329
1329 raise HTTPFound(default_redirect_url)
1330 raise HTTPFound(default_redirect_url)
1330
1331
1331 @LoginRequired()
1332 @LoginRequired()
1332 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1333 def repo_files_add_file(self):
1334 def repo_files_add_file(self):
1334 _ = self.request.translate
1335 _ = self.request.translate
1335 c = self.load_default_context()
1336 c = self.load_default_context()
1336 commit_id, f_path = self._get_commit_and_path()
1337 commit_id, f_path = self._get_commit_and_path()
1337
1338
1338 self._ensure_not_locked()
1339 self._ensure_not_locked()
1339
1340
1340 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1341 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1341 if c.commit is None:
1342 if c.commit is None:
1342 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1343 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1343
1344
1344 if self.rhodecode_vcs_repo.is_empty():
1345 if self.rhodecode_vcs_repo.is_empty():
1345 # for empty repository we cannot check for current branch, we rely on
1346 # for empty repository we cannot check for current branch, we rely on
1346 # c.commit.branch instead
1347 # c.commit.branch instead
1347 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1348 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1348 else:
1349 else:
1349 _branch_name, _sha_commit_id, is_head = \
1350 _branch_name, _sha_commit_id, is_head = \
1350 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1351 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1351 landing_ref=self.db_repo.landing_ref_name)
1352 landing_ref=self.db_repo.landing_ref_name)
1352
1353
1353 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1354 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1354 self.check_branch_permission(_branch_name, commit_id=commit_id)
1355 self.check_branch_permission(_branch_name, commit_id=commit_id)
1355
1356
1356 c.default_message = (_('Added file via RhodeCode Enterprise'))
1357 c.default_message = (_('Added file via RhodeCode Enterprise'))
1357 c.f_path = f_path.lstrip('/') # ensure not relative path
1358 c.f_path = f_path.lstrip('/') # ensure not relative path
1358
1359
1359 return self._get_template_context(c)
1360 return self._get_template_context(c)
1360
1361
1361 @LoginRequired()
1362 @LoginRequired()
1362 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1363 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1363 @CSRFRequired()
1364 @CSRFRequired()
1364 def repo_files_create_file(self):
1365 def repo_files_create_file(self):
1365 _ = self.request.translate
1366 _ = self.request.translate
1366 c = self.load_default_context()
1367 c = self.load_default_context()
1367 commit_id, f_path = self._get_commit_and_path()
1368 commit_id, f_path = self._get_commit_and_path()
1368
1369
1369 self._ensure_not_locked()
1370 self._ensure_not_locked()
1370
1371
1371 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1372 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1372 if c.commit is None:
1373 if c.commit is None:
1373 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1374 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1374
1375
1375 # calculate redirect URL
1376 # calculate redirect URL
1376 if self.rhodecode_vcs_repo.is_empty():
1377 if self.rhodecode_vcs_repo.is_empty():
1377 default_redirect_url = h.route_path(
1378 default_redirect_url = h.route_path(
1378 'repo_summary', repo_name=self.db_repo_name)
1379 'repo_summary', repo_name=self.db_repo_name)
1379 else:
1380 else:
1380 default_redirect_url = h.route_path(
1381 default_redirect_url = h.route_path(
1381 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1382 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1382
1383
1383 if self.rhodecode_vcs_repo.is_empty():
1384 if self.rhodecode_vcs_repo.is_empty():
1384 # for empty repository we cannot check for current branch, we rely on
1385 # for empty repository we cannot check for current branch, we rely on
1385 # c.commit.branch instead
1386 # c.commit.branch instead
1386 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1387 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1387 else:
1388 else:
1388 _branch_name, _sha_commit_id, is_head = \
1389 _branch_name, _sha_commit_id, is_head = \
1389 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1390 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1390 landing_ref=self.db_repo.landing_ref_name)
1391 landing_ref=self.db_repo.landing_ref_name)
1391
1392
1392 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1393 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1393 self.check_branch_permission(_branch_name, commit_id=commit_id)
1394 self.check_branch_permission(_branch_name, commit_id=commit_id)
1394
1395
1395 c.default_message = (_('Added file via RhodeCode Enterprise'))
1396 c.default_message = (_('Added file via RhodeCode Enterprise'))
1396 c.f_path = f_path
1397 c.f_path = f_path
1397
1398
1398 r_post = self.request.POST
1399 r_post = self.request.POST
1399 message = r_post.get('message') or c.default_message
1400 message = r_post.get('message') or c.default_message
1400 filename = r_post.get('filename')
1401 filename = r_post.get('filename')
1401 unix_mode = 0
1402 unix_mode = 0
1402
1403
1403 if not filename:
1404 if not filename:
1404 # If there's no commit, redirect to repo summary
1405 # If there's no commit, redirect to repo summary
1405 if type(c.commit) is EmptyCommit:
1406 if type(c.commit) is EmptyCommit:
1406 redirect_url = h.route_path(
1407 redirect_url = h.route_path(
1407 'repo_summary', repo_name=self.db_repo_name)
1408 'repo_summary', repo_name=self.db_repo_name)
1408 else:
1409 else:
1409 redirect_url = default_redirect_url
1410 redirect_url = default_redirect_url
1410 h.flash(_('No filename specified'), category='warning')
1411 h.flash(_('No filename specified'), category='warning')
1411 raise HTTPFound(redirect_url)
1412 raise HTTPFound(redirect_url)
1412
1413
1413 root_path = f_path
1414 root_path = f_path
1414 pure_path = self.create_pure_path(root_path, filename)
1415 pure_path = self.create_pure_path(root_path, filename)
1415 node_path = pure_path.as_posix().lstrip('/')
1416 node_path = pure_path.as_posix().lstrip('/')
1416
1417
1417 author = self._rhodecode_db_user.full_contact
1418 author = self._rhodecode_db_user.full_contact
1418 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1419 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1419 nodes = {
1420 nodes = {
1420 safe_bytes(node_path): {
1421 safe_bytes(node_path): {
1421 'content': safe_bytes(content)
1422 'content': safe_bytes(content)
1422 }
1423 }
1423 }
1424 }
1424
1425
1425 try:
1426 try:
1426
1427
1427 commit = ScmModel().create_nodes(
1428 commit = ScmModel().create_nodes(
1428 user=self._rhodecode_db_user.user_id,
1429 user=self._rhodecode_db_user.user_id,
1429 repo=self.db_repo,
1430 repo=self.db_repo,
1430 message=message,
1431 message=message,
1431 nodes=nodes,
1432 nodes=nodes,
1432 parent_commit=c.commit,
1433 parent_commit=c.commit,
1433 author=author,
1434 author=author,
1434 )
1435 )
1435
1436
1436 h.flash(_('Successfully committed new file `{}`').format(
1437 h.flash(_('Successfully committed new file `{}`').format(
1437 h.escape(node_path)), category='success')
1438 h.escape(node_path)), category='success')
1438
1439
1439 default_redirect_url = h.route_path(
1440 default_redirect_url = h.route_path(
1440 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1441 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1441
1442
1442 except NonRelativePathError:
1443 except NonRelativePathError:
1443 log.exception('Non Relative path found')
1444 log.exception('Non Relative path found')
1444 h.flash(_('The location specified must be a relative path and must not '
1445 h.flash(_('The location specified must be a relative path and must not '
1445 'contain .. in the path'), category='warning')
1446 'contain .. in the path'), category='warning')
1446 raise HTTPFound(default_redirect_url)
1447 raise HTTPFound(default_redirect_url)
1447 except (NodeError, NodeAlreadyExistsError) as e:
1448 except (NodeError, NodeAlreadyExistsError) as e:
1448 h.flash(h.escape(safe_str(e)), category='error')
1449 h.flash(h.escape(safe_str(e)), category='error')
1449 except Exception:
1450 except Exception:
1450 log.exception('Error occurred during commit')
1451 log.exception('Error occurred during commit')
1451 h.flash(_('Error occurred during commit'), category='error')
1452 h.flash(_('Error occurred during commit'), category='error')
1452
1453
1453 raise HTTPFound(default_redirect_url)
1454 raise HTTPFound(default_redirect_url)
1454
1455
1455 @LoginRequired()
1456 @LoginRequired()
1456 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1457 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1457 @CSRFRequired()
1458 @CSRFRequired()
1458 def repo_files_upload_file(self):
1459 def repo_files_upload_file(self):
1459 _ = self.request.translate
1460 _ = self.request.translate
1460 c = self.load_default_context()
1461 c = self.load_default_context()
1461 commit_id, f_path = self._get_commit_and_path()
1462 commit_id, f_path = self._get_commit_and_path()
1462
1463
1463 self._ensure_not_locked()
1464 self._ensure_not_locked()
1464
1465
1465 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1466 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1466 if c.commit is None:
1467 if c.commit is None:
1467 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1468 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1468
1469
1469 # calculate redirect URL
1470 # calculate redirect URL
1470 if self.rhodecode_vcs_repo.is_empty():
1471 if self.rhodecode_vcs_repo.is_empty():
1471 default_redirect_url = h.route_path(
1472 default_redirect_url = h.route_path(
1472 'repo_summary', repo_name=self.db_repo_name)
1473 'repo_summary', repo_name=self.db_repo_name)
1473 else:
1474 else:
1474 default_redirect_url = h.route_path(
1475 default_redirect_url = h.route_path(
1475 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1476 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1476
1477
1477 if self.rhodecode_vcs_repo.is_empty():
1478 if self.rhodecode_vcs_repo.is_empty():
1478 # for empty repository we cannot check for current branch, we rely on
1479 # for empty repository we cannot check for current branch, we rely on
1479 # c.commit.branch instead
1480 # c.commit.branch instead
1480 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1481 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1481 else:
1482 else:
1482 _branch_name, _sha_commit_id, is_head = \
1483 _branch_name, _sha_commit_id, is_head = \
1483 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1484 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1484 landing_ref=self.db_repo.landing_ref_name)
1485 landing_ref=self.db_repo.landing_ref_name)
1485
1486
1486 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1487 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1487 if error:
1488 if error:
1488 return {
1489 return {
1489 'error': error,
1490 'error': error,
1490 'redirect_url': default_redirect_url
1491 'redirect_url': default_redirect_url
1491 }
1492 }
1492 error = self.check_branch_permission(_branch_name, json_mode=True)
1493 error = self.check_branch_permission(_branch_name, json_mode=True)
1493 if error:
1494 if error:
1494 return {
1495 return {
1495 'error': error,
1496 'error': error,
1496 'redirect_url': default_redirect_url
1497 'redirect_url': default_redirect_url
1497 }
1498 }
1498
1499
1499 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1500 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1500 c.f_path = f_path
1501 c.f_path = f_path
1501
1502
1502 r_post = self.request.POST
1503 r_post = self.request.POST
1503
1504
1504 message = c.default_message
1505 message = c.default_message
1505 user_message = r_post.getall('message')
1506 user_message = r_post.getall('message')
1506 if isinstance(user_message, list) and user_message:
1507 if isinstance(user_message, list) and user_message:
1507 # we take the first from duplicated results if it's not empty
1508 # we take the first from duplicated results if it's not empty
1508 message = user_message[0] if user_message[0] else message
1509 message = user_message[0] if user_message[0] else message
1509
1510
1510 nodes = {}
1511 nodes = {}
1511
1512
1512 for file_obj in r_post.getall('files_upload') or []:
1513 for file_obj in r_post.getall('files_upload') or []:
1513 content = file_obj.file
1514 content = file_obj.file
1514 filename = file_obj.filename
1515 filename = file_obj.filename
1515
1516
1516 root_path = f_path
1517 root_path = f_path
1517 pure_path = self.create_pure_path(root_path, filename)
1518 pure_path = self.create_pure_path(root_path, filename)
1518 node_path = pure_path.as_posix().lstrip('/')
1519 node_path = pure_path.as_posix().lstrip('/')
1519
1520
1520 nodes[safe_bytes(node_path)] = {
1521 nodes[safe_bytes(node_path)] = {
1521 'content': content
1522 'content': content
1522 }
1523 }
1523
1524
1524 if not nodes:
1525 if not nodes:
1525 error = 'missing files'
1526 error = 'missing files'
1526 return {
1527 return {
1527 'error': error,
1528 'error': error,
1528 'redirect_url': default_redirect_url
1529 'redirect_url': default_redirect_url
1529 }
1530 }
1530
1531
1531 author = self._rhodecode_db_user.full_contact
1532 author = self._rhodecode_db_user.full_contact
1532
1533
1533 try:
1534 try:
1534 commit = ScmModel().create_nodes(
1535 commit = ScmModel().create_nodes(
1535 user=self._rhodecode_db_user.user_id,
1536 user=self._rhodecode_db_user.user_id,
1536 repo=self.db_repo,
1537 repo=self.db_repo,
1537 message=message,
1538 message=message,
1538 nodes=nodes,
1539 nodes=nodes,
1539 parent_commit=c.commit,
1540 parent_commit=c.commit,
1540 author=author,
1541 author=author,
1541 )
1542 )
1542 if len(nodes) == 1:
1543 if len(nodes) == 1:
1543 flash_message = _('Successfully committed {} new files').format(len(nodes))
1544 flash_message = _('Successfully committed {} new files').format(len(nodes))
1544 else:
1545 else:
1545 flash_message = _('Successfully committed 1 new file')
1546 flash_message = _('Successfully committed 1 new file')
1546
1547
1547 h.flash(flash_message, category='success')
1548 h.flash(flash_message, category='success')
1548
1549
1549 default_redirect_url = h.route_path(
1550 default_redirect_url = h.route_path(
1550 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1551 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1551
1552
1552 except NonRelativePathError:
1553 except NonRelativePathError:
1553 log.exception('Non Relative path found')
1554 log.exception('Non Relative path found')
1554 error = _('The location specified must be a relative path and must not '
1555 error = _('The location specified must be a relative path and must not '
1555 'contain .. in the path')
1556 'contain .. in the path')
1556 h.flash(error, category='warning')
1557 h.flash(error, category='warning')
1557
1558
1558 return {
1559 return {
1559 'error': error,
1560 'error': error,
1560 'redirect_url': default_redirect_url
1561 'redirect_url': default_redirect_url
1561 }
1562 }
1562 except (NodeError, NodeAlreadyExistsError) as e:
1563 except (NodeError, NodeAlreadyExistsError) as e:
1563 error = h.escape(e)
1564 error = h.escape(e)
1564 h.flash(error, category='error')
1565 h.flash(error, category='error')
1565
1566
1566 return {
1567 return {
1567 'error': error,
1568 'error': error,
1568 'redirect_url': default_redirect_url
1569 'redirect_url': default_redirect_url
1569 }
1570 }
1570 except Exception:
1571 except Exception:
1571 log.exception('Error occurred during commit')
1572 log.exception('Error occurred during commit')
1572 error = _('Error occurred during commit')
1573 error = _('Error occurred during commit')
1573 h.flash(error, category='error')
1574 h.flash(error, category='error')
1574 return {
1575 return {
1575 'error': error,
1576 'error': error,
1576 'redirect_url': default_redirect_url
1577 'redirect_url': default_redirect_url
1577 }
1578 }
1578
1579
1579 return {
1580 return {
1580 'error': None,
1581 'error': None,
1581 'redirect_url': default_redirect_url
1582 'redirect_url': default_redirect_url
1582 }
1583 }
@@ -1,203 +1,206 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20
20
21 from rhodecode.translation import lazy_ugettext
21 from rhodecode.translation import lazy_ugettext
22 from rhodecode.events.repo import (RepoEvent, _commits_as_dict, _issues_as_dict)
22 from rhodecode.events.repo import (RepoEvent, _commits_as_dict, _issues_as_dict)
23
23
24 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
25
25
26
26
27 class PullRequestEvent(RepoEvent):
27 class PullRequestEvent(RepoEvent):
28 """
28 """
29 Base class for pull request events.
29 Base class for pull request events.
30
30
31 :param pullrequest: a :class:`PullRequest` instance
31 :param pullrequest: a :class:`PullRequest` instance
32 """
32 """
33 name = 'pullrequest-event'
34 display_name = lazy_ugettext('pullrequest generic event')
35 description = lazy_ugettext('All events within a context of a pull request')
33
36
34 def __init__(self, pullrequest):
37 def __init__(self, pullrequest):
35 super().__init__(pullrequest.target_repo)
38 super().__init__(pullrequest.target_repo)
36 self.pullrequest = pullrequest
39 self.pullrequest = pullrequest
37
40
38 def as_dict(self):
41 def as_dict(self):
39 from rhodecode.lib.utils2 import md5_safe
42 from rhodecode.lib.utils2 import md5_safe
40 from rhodecode.model.pull_request import PullRequestModel
43 from rhodecode.model.pull_request import PullRequestModel
41 data = super().as_dict()
44 data = super().as_dict()
42
45
43 commits = _commits_as_dict(
46 commits = _commits_as_dict(
44 self,
47 self,
45 commit_ids=self.pullrequest.revisions,
48 commit_ids=self.pullrequest.revisions,
46 repos=[self.pullrequest.source_repo]
49 repos=[self.pullrequest.source_repo]
47 )
50 )
48 issues = _issues_as_dict(commits)
51 issues = _issues_as_dict(commits)
49 # calculate hashes of all commits for unique identifier of commits
52 # calculate hashes of all commits for unique identifier of commits
50 # inside that pull request
53 # inside that pull request
51 commits_hash = md5_safe(':'.join(x.get('raw_id', '') for x in commits))
54 commits_hash = md5_safe(':'.join(x.get('raw_id', '') for x in commits))
52
55
53 data.update({
56 data.update({
54 'pullrequest': {
57 'pullrequest': {
55 'title': self.pullrequest.title,
58 'title': self.pullrequest.title,
56 'issues': issues,
59 'issues': issues,
57 'pull_request_id': self.pullrequest.pull_request_id,
60 'pull_request_id': self.pullrequest.pull_request_id,
58 'url': PullRequestModel().get_url(
61 'url': PullRequestModel().get_url(
59 self.pullrequest, request=self.request),
62 self.pullrequest, request=self.request),
60 'permalink_url': PullRequestModel().get_url(
63 'permalink_url': PullRequestModel().get_url(
61 self.pullrequest, request=self.request, permalink=True),
64 self.pullrequest, request=self.request, permalink=True),
62 'shadow_url': PullRequestModel().get_shadow_clone_url(
65 'shadow_url': PullRequestModel().get_shadow_clone_url(
63 self.pullrequest, request=self.request),
66 self.pullrequest, request=self.request),
64 'status': self.pullrequest.calculated_review_status(),
67 'status': self.pullrequest.calculated_review_status(),
65 'commits_uid': commits_hash,
68 'commits_uid': commits_hash,
66 'commits': commits,
69 'commits': commits,
67 }
70 }
68 })
71 })
69 return data
72 return data
70
73
71
74
72 class PullRequestCreateEvent(PullRequestEvent):
75 class PullRequestCreateEvent(PullRequestEvent):
73 """
76 """
74 An instance of this class is emitted as an :term:`event` after a pull
77 An instance of this class is emitted as an :term:`event` after a pull
75 request is created.
78 request is created.
76 """
79 """
77 name = 'pullrequest-create'
80 name = 'pullrequest-create'
78 display_name = lazy_ugettext('pullrequest created')
81 display_name = lazy_ugettext('pullrequest created')
79 description = lazy_ugettext('Event triggered after pull request was created')
82 description = lazy_ugettext('Event triggered after pull request was created')
80
83
81
84
82 class PullRequestCloseEvent(PullRequestEvent):
85 class PullRequestCloseEvent(PullRequestEvent):
83 """
86 """
84 An instance of this class is emitted as an :term:`event` after a pull
87 An instance of this class is emitted as an :term:`event` after a pull
85 request is closed.
88 request is closed.
86 """
89 """
87 name = 'pullrequest-close'
90 name = 'pullrequest-close'
88 display_name = lazy_ugettext('pullrequest closed')
91 display_name = lazy_ugettext('pullrequest closed')
89 description = lazy_ugettext('Event triggered after pull request was closed')
92 description = lazy_ugettext('Event triggered after pull request was closed')
90
93
91
94
92 class PullRequestUpdateEvent(PullRequestEvent):
95 class PullRequestUpdateEvent(PullRequestEvent):
93 """
96 """
94 An instance of this class is emitted as an :term:`event` after a pull
97 An instance of this class is emitted as an :term:`event` after a pull
95 request's commits have been updated.
98 request's commits have been updated.
96 """
99 """
97 name = 'pullrequest-update'
100 name = 'pullrequest-update'
98 display_name = lazy_ugettext('pullrequest commits updated')
101 display_name = lazy_ugettext('pullrequest commits updated')
99 description = lazy_ugettext('Event triggered after pull requests was updated')
102 description = lazy_ugettext('Event triggered after pull requests was updated')
100
103
101
104
102 class PullRequestReviewEvent(PullRequestEvent):
105 class PullRequestReviewEvent(PullRequestEvent):
103 """
106 """
104 An instance of this class is emitted as an :term:`event` after a pull
107 An instance of this class is emitted as an :term:`event` after a pull
105 request review has changed. A status defines new status of review.
108 request review has changed. A status defines new status of review.
106 """
109 """
107 name = 'pullrequest-review'
110 name = 'pullrequest-review'
108 display_name = lazy_ugettext('pullrequest review changed')
111 display_name = lazy_ugettext('pullrequest review changed')
109 description = lazy_ugettext('Event triggered after a review status of a '
112 description = lazy_ugettext('Event triggered after a review status of a '
110 'pull requests has changed to other.')
113 'pull requests has changed to other.')
111
114
112 def __init__(self, pullrequest, status):
115 def __init__(self, pullrequest, status):
113 super().__init__(pullrequest)
116 super().__init__(pullrequest)
114 self.status = status
117 self.status = status
115
118
116
119
117 class PullRequestMergeEvent(PullRequestEvent):
120 class PullRequestMergeEvent(PullRequestEvent):
118 """
121 """
119 An instance of this class is emitted as an :term:`event` after a pull
122 An instance of this class is emitted as an :term:`event` after a pull
120 request is merged.
123 request is merged.
121 """
124 """
122 name = 'pullrequest-merge'
125 name = 'pullrequest-merge'
123 display_name = lazy_ugettext('pullrequest merged')
126 display_name = lazy_ugettext('pullrequest merged')
124 description = lazy_ugettext('Event triggered after a successful merge operation '
127 description = lazy_ugettext('Event triggered after a successful merge operation '
125 'was executed on a pull request')
128 'was executed on a pull request')
126
129
127
130
128 class PullRequestCommentEvent(PullRequestEvent):
131 class PullRequestCommentEvent(PullRequestEvent):
129 """
132 """
130 An instance of this class is emitted as an :term:`event` after a pull
133 An instance of this class is emitted as an :term:`event` after a pull
131 request comment is created.
134 request comment is created.
132 """
135 """
133 name = 'pullrequest-comment'
136 name = 'pullrequest-comment'
134 display_name = lazy_ugettext('pullrequest commented')
137 display_name = lazy_ugettext('pullrequest commented')
135 description = lazy_ugettext('Event triggered after a comment was made on a code '
138 description = lazy_ugettext('Event triggered after a comment was made on a code '
136 'in the pull request')
139 'in the pull request')
137
140
138 def __init__(self, pullrequest, comment):
141 def __init__(self, pullrequest, comment):
139 super().__init__(pullrequest)
142 super().__init__(pullrequest)
140 self.comment = comment
143 self.comment = comment
141
144
142 def as_dict(self):
145 def as_dict(self):
143 from rhodecode.model.comment import CommentsModel
146 from rhodecode.model.comment import CommentsModel
144 data = super().as_dict()
147 data = super().as_dict()
145
148
146 status = None
149 status = None
147 if self.comment.status_change:
150 if self.comment.status_change:
148 status = self.comment.review_status
151 status = self.comment.review_status
149
152
150 data.update({
153 data.update({
151 'comment': {
154 'comment': {
152 'status': status,
155 'status': status,
153 'text': self.comment.text,
156 'text': self.comment.text,
154 'type': self.comment.comment_type,
157 'type': self.comment.comment_type,
155 'file': self.comment.f_path,
158 'file': self.comment.f_path,
156 'line': self.comment.line_no,
159 'line': self.comment.line_no,
157 'version': self.comment.last_version,
160 'version': self.comment.last_version,
158 'url': CommentsModel().get_url(
161 'url': CommentsModel().get_url(
159 self.comment, request=self.request),
162 self.comment, request=self.request),
160 'permalink_url': CommentsModel().get_url(
163 'permalink_url': CommentsModel().get_url(
161 self.comment, request=self.request, permalink=True),
164 self.comment, request=self.request, permalink=True),
162 }
165 }
163 })
166 })
164 return data
167 return data
165
168
166
169
167 class PullRequestCommentEditEvent(PullRequestEvent):
170 class PullRequestCommentEditEvent(PullRequestEvent):
168 """
171 """
169 An instance of this class is emitted as an :term:`event` after a pull
172 An instance of this class is emitted as an :term:`event` after a pull
170 request comment is edited.
173 request comment is edited.
171 """
174 """
172 name = 'pullrequest-comment-edit'
175 name = 'pullrequest-comment-edit'
173 display_name = lazy_ugettext('pullrequest comment edited')
176 display_name = lazy_ugettext('pullrequest comment edited')
174 description = lazy_ugettext('Event triggered after a comment was edited on a code '
177 description = lazy_ugettext('Event triggered after a comment was edited on a code '
175 'in the pull request')
178 'in the pull request')
176
179
177 def __init__(self, pullrequest, comment):
180 def __init__(self, pullrequest, comment):
178 super().__init__(pullrequest)
181 super().__init__(pullrequest)
179 self.comment = comment
182 self.comment = comment
180
183
181 def as_dict(self):
184 def as_dict(self):
182 from rhodecode.model.comment import CommentsModel
185 from rhodecode.model.comment import CommentsModel
183 data = super().as_dict()
186 data = super().as_dict()
184
187
185 status = None
188 status = None
186 if self.comment.status_change:
189 if self.comment.status_change:
187 status = self.comment.review_status
190 status = self.comment.review_status
188
191
189 data.update({
192 data.update({
190 'comment': {
193 'comment': {
191 'status': status,
194 'status': status,
192 'text': self.comment.text,
195 'text': self.comment.text,
193 'type': self.comment.comment_type,
196 'type': self.comment.comment_type,
194 'file': self.comment.f_path,
197 'file': self.comment.f_path,
195 'line': self.comment.line_no,
198 'line': self.comment.line_no,
196 'version': self.comment.last_version,
199 'version': self.comment.last_version,
197 'url': CommentsModel().get_url(
200 'url': CommentsModel().get_url(
198 self.comment, request=self.request),
201 self.comment, request=self.request),
199 'permalink_url': CommentsModel().get_url(
202 'permalink_url': CommentsModel().get_url(
200 self.comment, request=self.request, permalink=True),
203 self.comment, request=self.request, permalink=True),
201 }
204 }
202 })
205 })
203 return data
206 return data
@@ -1,440 +1,444 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import collections
19 import collections
20 import logging
20 import logging
21 import datetime
21 import datetime
22
22
23 from rhodecode.translation import lazy_ugettext
23 from rhodecode.translation import lazy_ugettext
24 from rhodecode.model.db import User, Repository, Session
24 from rhodecode.model.db import User, Repository
25 from rhodecode.events.base import RhodeCodeIntegrationEvent
25 from rhodecode.events.base import RhodeCodeIntegrationEvent
26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 def _commits_as_dict(event, commit_ids, repos):
31 def _commits_as_dict(event, commit_ids, repos):
32 """
32 """
33 Helper function to serialize commit_ids
33 Helper function to serialize commit_ids
34
34
35 :param event: class calling this method
35 :param event: class calling this method
36 :param commit_ids: commits to get
36 :param commit_ids: commits to get
37 :param repos: list of repos to check
37 :param repos: a list of repos to check
38 """
38 """
39 from rhodecode.lib.utils2 import extract_mentioned_users
39 from rhodecode.lib.utils2 import extract_mentioned_users
40 from rhodecode.lib.helpers import (
40 from rhodecode.lib.helpers import (
41 urlify_commit_message, process_patterns, chop_at_smart)
41 urlify_commit_message, process_patterns, chop_at_smart)
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43
43
44 if not repos:
44 if not repos:
45 raise Exception('no repo defined')
45 raise Exception('no repo defined')
46
46
47 if not isinstance(repos, (tuple, list)):
47 if not isinstance(repos, (tuple, list)):
48 repos = [repos]
48 repos = [repos]
49
49
50 if not commit_ids:
50 if not commit_ids:
51 return []
51 return []
52
52
53 needed_commits = list(commit_ids)
53 needed_commits = list(commit_ids)
54
54
55 commits = []
55 commits = []
56 reviewers = []
56 reviewers = []
57 for repo in repos:
57 for repo in repos:
58 if not needed_commits:
58 if not needed_commits:
59 return commits # return early if we have the commits we need
59 return commits # return early if we have the commits we need
60
60
61 vcs_repo = repo.scm_instance(cache=False)
61 vcs_repo = repo.scm_instance(cache=False)
62
62
63 try:
63 try:
64 # use copy of needed_commits since we modify it while iterating
64 # use copy of needed_commits since we modify it while iterating
65 for commit_id in list(needed_commits):
65 for commit_id in list(needed_commits):
66 if commit_id.startswith('tag=>'):
66 if commit_id.startswith('tag=>'):
67 raw_id = commit_id[5:]
67 raw_id = commit_id[5:]
68 cs_data = {
68 cs_data = {
69 'raw_id': commit_id, 'short_id': commit_id,
69 'raw_id': commit_id, 'short_id': commit_id,
70 'branch': None,
70 'branch': None,
71 'git_ref_change': 'tag_add',
71 'git_ref_change': 'tag_add',
72 'message': f'Added new tag {raw_id}',
72 'message': f'Added new tag {raw_id}',
73 'author': event.actor.full_contact,
73 'author': event.actor.full_contact,
74 'date': datetime.datetime.now(),
74 'date': datetime.datetime.now(),
75 'refs': {
75 'refs': {
76 'branches': [],
76 'branches': [],
77 'bookmarks': [],
77 'bookmarks': [],
78 'tags': []
78 'tags': []
79 }
79 }
80 }
80 }
81 commits.append(cs_data)
81 commits.append(cs_data)
82
82
83 elif commit_id.startswith('delete_branch=>'):
83 elif commit_id.startswith('delete_branch=>'):
84 raw_id = commit_id[15:]
84 raw_id = commit_id[15:]
85 cs_data = {
85 cs_data = {
86 'raw_id': commit_id, 'short_id': commit_id,
86 'raw_id': commit_id, 'short_id': commit_id,
87 'branch': None,
87 'branch': None,
88 'git_ref_change': 'branch_delete',
88 'git_ref_change': 'branch_delete',
89 'message': f'Deleted branch {raw_id}',
89 'message': f'Deleted branch {raw_id}',
90 'author': event.actor.full_contact,
90 'author': event.actor.full_contact,
91 'date': datetime.datetime.now(),
91 'date': datetime.datetime.now(),
92 'refs': {
92 'refs': {
93 'branches': [],
93 'branches': [],
94 'bookmarks': [],
94 'bookmarks': [],
95 'tags': []
95 'tags': []
96 }
96 }
97 }
97 }
98 commits.append(cs_data)
98 commits.append(cs_data)
99
99
100 else:
100 else:
101 try:
101 try:
102 cs = vcs_repo.get_commit(commit_id)
102 cs = vcs_repo.get_commit(commit_id)
103 except CommitDoesNotExistError:
103 except CommitDoesNotExistError:
104 continue # maybe its in next repo
104 continue # maybe its in next repo
105
105
106 cs_data = cs.__json__()
106 cs_data = cs.__json__()
107 cs_data['refs'] = cs._get_refs()
107 cs_data['refs'] = cs._get_refs()
108
108
109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
110 cs_data['reviewers'] = reviewers
110 cs_data['reviewers'] = reviewers
111 cs_data['url'] = RepoModel().get_commit_url(
111 cs_data['url'] = RepoModel().get_commit_url(
112 repo, cs_data['raw_id'], request=event.request)
112 repo, cs_data['raw_id'], request=event.request)
113 cs_data['permalink_url'] = RepoModel().get_commit_url(
113 cs_data['permalink_url'] = RepoModel().get_commit_url(
114 repo, cs_data['raw_id'], request=event.request,
114 repo, cs_data['raw_id'], request=event.request,
115 permalink=True)
115 permalink=True)
116 urlified_message, issues_data, errors = process_patterns(
116 urlified_message, issues_data, errors = process_patterns(
117 cs_data['message'], repo.repo_name)
117 cs_data['message'], repo.repo_name)
118 cs_data['issues'] = issues_data
118 cs_data['issues'] = issues_data
119 cs_data['message_html'] = urlify_commit_message(
119 cs_data['message_html'] = urlify_commit_message(
120 cs_data['message'], repo.repo_name)
120 cs_data['message'], repo.repo_name)
121 cs_data['message_html_title'] = chop_at_smart(
121 cs_data['message_html_title'] = chop_at_smart(
122 cs_data['message'], '\n', suffix_if_chopped='...')
122 cs_data['message'], '\n', suffix_if_chopped='...')
123 commits.append(cs_data)
123 commits.append(cs_data)
124
124
125 needed_commits.remove(commit_id)
125 needed_commits.remove(commit_id)
126
126
127 except Exception:
127 except Exception:
128 log.exception('Failed to extract commits data')
128 log.exception('Failed to extract commits data')
129 # we don't send any commits when crash happens, only full list
129 # we don't send any commits when crash happens, only full list
130 # matters we short circuit then.
130 # matters we short circuit then.
131 return []
131 return []
132
132
133 # we failed to remove all needed_commits from all repositories
133 # we failed to remove all needed_commits from all repositories
134 if needed_commits:
134 if needed_commits:
135 raise ValueError(f'Unexpectedly not found {needed_commits} in all available repos {repos}')
135 raise ValueError(f'Unexpectedly not found {needed_commits} in all available repos {repos}')
136
136
137 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
137 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
138 if missing_commits:
138 if missing_commits:
139 log.error('Inconsistent repository state. '
139 log.error('Inconsistent repository state. '
140 'Missing commits: %s', ', '.join(missing_commits))
140 'Missing commits: %s', ', '.join(missing_commits))
141
141
142 return commits
142 return commits
143
143
144
144
145 def _issues_as_dict(commits):
145 def _issues_as_dict(commits):
146 """ Helper function to serialize issues from commits """
146 """ Helper function to serialize issues from commits """
147 issues = {}
147 issues = {}
148 for commit in commits:
148 for commit in commits:
149 for issue in commit['issues']:
149 for issue in commit['issues']:
150 issues[issue['id']] = issue
150 issues[issue['id']] = issue
151 return issues
151 return issues
152
152
153
153
154 class RepoEvent(RhodeCodeIntegrationEvent):
154 class RepoEvent(RhodeCodeIntegrationEvent):
155 """
155 """
156 Base class for events acting on a repository.
156 Base class for events acting on a repository.
157
158 :param repo: a :class:`Repository` instance
159 """
157 """
160
158
161 def __init__(self, repo):
159 def __init__(self, repo):
160 """
161 :param repo: a :class:`Repository` instance
162 """
162 super().__init__()
163 super().__init__()
163 self.repo = repo
164 self.repo = repo
164
165
165 def as_dict(self):
166 def as_dict(self):
166 from rhodecode.model.repo import RepoModel
167 from rhodecode.model.repo import RepoModel
167 data = super().as_dict()
168 data = super().as_dict()
168
169
169 extra_fields = collections.OrderedDict()
170 extra_fields = collections.OrderedDict()
170 for field in self.repo.extra_fields:
171 for field in self.repo.extra_fields:
171 extra_fields[field.field_key] = field.field_value
172 extra_fields[field.field_key] = field.field_value
172
173
173 data.update({
174 data.update({
174 'repo': {
175 'repo': {
175 'repo_id': self.repo.repo_id,
176 'repo_id': self.repo.repo_id,
176 'repo_name': self.repo.repo_name,
177 'repo_name': self.repo.repo_name,
177 'repo_type': self.repo.repo_type,
178 'repo_type': self.repo.repo_type,
178 'url': RepoModel().get_url(
179 'url': RepoModel().get_url(
179 self.repo, request=self.request),
180 self.repo, request=self.request),
180 'permalink_url': RepoModel().get_url(
181 'permalink_url': RepoModel().get_url(
181 self.repo, request=self.request, permalink=True),
182 self.repo, request=self.request, permalink=True),
182 'extra_fields': extra_fields
183 'extra_fields': extra_fields
183 }
184 }
184 })
185 })
185 return data
186 return data
186
187
187
188
188 class RepoCommitCommentEvent(RepoEvent):
189 class RepoCommitCommentEvent(RepoEvent):
189 """
190 """
190 An instance of this class is emitted as an :term:`event` after a comment is made
191 An instance of this class is emitted as an :term:`event` after a comment is made
191 on repository commit.
192 on repository commit.
192 """
193 """
193
194
194 name = 'repo-commit-comment'
195 name = 'repo-commit-comment'
195 display_name = lazy_ugettext('repository commit comment')
196 display_name = lazy_ugettext('repository commit comment')
196 description = lazy_ugettext('Event triggered after a comment was made '
197 description = lazy_ugettext('Event triggered after a comment was made '
197 'on commit inside a repository')
198 'on commit inside a repository')
198
199
199 def __init__(self, repo, commit, comment):
200 def __init__(self, repo, commit, comment):
200 super().__init__(repo)
201 super().__init__(repo)
201 self.commit = commit
202 self.commit = commit
202 self.comment = comment
203 self.comment = comment
203
204
204 def as_dict(self):
205 def as_dict(self):
205 data = super().as_dict()
206 data = super().as_dict()
206 data['commit'] = {
207 data['commit'] = {
207 'commit_id': self.commit.raw_id,
208 'commit_id': self.commit.raw_id,
208 'commit_message': self.commit.message,
209 'commit_message': self.commit.message,
209 'commit_branch': self.commit.branch,
210 'commit_branch': self.commit.branch,
210 }
211 }
211
212
212 data['comment'] = {
213 data['comment'] = {
213 'comment_id': self.comment.comment_id,
214 'comment_id': self.comment.comment_id,
214 'comment_text': self.comment.text,
215 'comment_text': self.comment.text,
215 'comment_type': self.comment.comment_type,
216 'comment_type': self.comment.comment_type,
216 'comment_f_path': self.comment.f_path,
217 'comment_f_path': self.comment.f_path,
217 'comment_line_no': self.comment.line_no,
218 'comment_line_no': self.comment.line_no,
218 'comment_version': self.comment.last_version,
219 'comment_version': self.comment.last_version,
219 }
220 }
220 return data
221 return data
221
222
222
223
223 class RepoCommitCommentEditEvent(RepoEvent):
224 class RepoCommitCommentEditEvent(RepoEvent):
224 """
225 """
225 An instance of this class is emitted as an :term:`event` after a comment is edited
226 An instance of this class is emitted as an :term:`event` after a comment is edited
226 on repository commit.
227 on repository commit.
227 """
228 """
228
229
229 name = 'repo-commit-edit-comment'
230 name = 'repo-commit-edit-comment'
230 display_name = lazy_ugettext('repository commit edit comment')
231 display_name = lazy_ugettext('repository commit edit comment')
231 description = lazy_ugettext('Event triggered after a comment was edited '
232 description = lazy_ugettext('Event triggered after a comment was edited '
232 'on commit inside a repository')
233 'on commit inside a repository')
233
234
234 def __init__(self, repo, commit, comment):
235 def __init__(self, repo, commit, comment):
235 super().__init__(repo)
236 super().__init__(repo)
236 self.commit = commit
237 self.commit = commit
237 self.comment = comment
238 self.comment = comment
238
239
239 def as_dict(self):
240 def as_dict(self):
240 data = super().as_dict()
241 data = super().as_dict()
241 data['commit'] = {
242 data['commit'] = {
242 'commit_id': self.commit.raw_id,
243 'commit_id': self.commit.raw_id,
243 'commit_message': self.commit.message,
244 'commit_message': self.commit.message,
244 'commit_branch': self.commit.branch,
245 'commit_branch': self.commit.branch,
245 }
246 }
246
247
247 data['comment'] = {
248 data['comment'] = {
248 'comment_id': self.comment.comment_id,
249 'comment_id': self.comment.comment_id,
249 'comment_text': self.comment.text,
250 'comment_text': self.comment.text,
250 'comment_type': self.comment.comment_type,
251 'comment_type': self.comment.comment_type,
251 'comment_f_path': self.comment.f_path,
252 'comment_f_path': self.comment.f_path,
252 'comment_line_no': self.comment.line_no,
253 'comment_line_no': self.comment.line_no,
253 'comment_version': self.comment.last_version,
254 'comment_version': self.comment.last_version,
254 }
255 }
255 return data
256 return data
256
257
257
258
258 class RepoPreCreateEvent(RepoEvent):
259 class RepoPreCreateEvent(RepoEvent):
259 """
260 """
260 An instance of this class is emitted as an :term:`event` before a repo is
261 An instance of this class is emitted as an :term:`event` before a repo is
261 created.
262 created.
262 """
263 """
263 name = 'repo-pre-create'
264 name = 'repo-pre-create'
264 display_name = lazy_ugettext('repository pre create')
265 display_name = lazy_ugettext('repository pre create')
265 description = lazy_ugettext('Event triggered before repository is created')
266 description = lazy_ugettext('Event triggered before repository is created')
266
267
267
268
268 class RepoCreateEvent(RepoEvent):
269 class RepoCreateEvent(RepoEvent):
269 """
270 """
270 An instance of this class is emitted as an :term:`event` whenever a repo is
271 An instance of this class is emitted as an :term:`event` whenever a repo is
271 created.
272 created.
272 """
273 """
273 name = 'repo-create'
274 name = 'repo-create'
274 display_name = lazy_ugettext('repository created')
275 display_name = lazy_ugettext('repository created')
275 description = lazy_ugettext('Event triggered after repository was created')
276 description = lazy_ugettext('Event triggered after repository was created')
276
277
277
278
278 class RepoPreDeleteEvent(RepoEvent):
279 class RepoPreDeleteEvent(RepoEvent):
279 """
280 """
280 An instance of this class is emitted as an :term:`event` whenever a repo is
281 An instance of this class is emitted as an :term:`event` whenever a repo is
281 created.
282 created.
282 """
283 """
283 name = 'repo-pre-delete'
284 name = 'repo-pre-delete'
284 display_name = lazy_ugettext('repository pre delete')
285 display_name = lazy_ugettext('repository pre delete')
285 description = lazy_ugettext('Event triggered before a repository is deleted')
286 description = lazy_ugettext('Event triggered before a repository is deleted')
286
287
287
288
288 class RepoDeleteEvent(RepoEvent):
289 class RepoDeleteEvent(RepoEvent):
289 """
290 """
290 An instance of this class is emitted as an :term:`event` whenever a repo is
291 An instance of this class is emitted as an :term:`event` whenever a repo is
291 created.
292 created.
292 """
293 """
293 name = 'repo-delete'
294 name = 'repo-delete'
294 display_name = lazy_ugettext('repository deleted')
295 display_name = lazy_ugettext('repository deleted')
295 description = lazy_ugettext('Event triggered after repository was deleted')
296 description = lazy_ugettext('Event triggered after repository was deleted')
296
297
297
298
298 class RepoVCSEvent(RepoEvent):
299 class RepoVCSEvent(RepoEvent):
299 """
300 """
300 Base class for events triggered by the VCS
301 Base class for events triggered by the VCS
301 """
302 """
303 name = ''
304 display_name = 'generic_vcs_event'
305
302 def __init__(self, repo_name, extras):
306 def __init__(self, repo_name, extras):
303 self.repo = Repository.get_by_repo_name(repo_name)
307 self.repo = Repository.get_by_repo_name(repo_name)
304 if not self.repo:
308 if not self.repo:
305 raise Exception(f'repo by this name {repo_name} does not exist')
309 raise Exception(f'repo by this name {repo_name} does not exist')
306 self.extras = extras
310 self.extras = extras
307 super().__init__(self.repo)
311 super().__init__(self.repo)
308
312
309 @property
313 @property
310 def actor(self):
314 def actor(self):
311 if self.extras.get('username'):
315 if self.extras.get('username'):
312 return User.get_by_username(self.extras['username'])
316 return User.get_by_username(self.extras['username'])
313
317
314 @property
318 @property
315 def actor_ip(self):
319 def actor_ip(self):
316 if self.extras.get('ip'):
320 if self.extras.get('ip'):
317 return self.extras['ip']
321 return self.extras['ip']
318
322
319 @property
323 @property
320 def server_url(self):
324 def server_url(self):
321 if self.extras.get('server_url'):
325 if self.extras.get('server_url'):
322 return self.extras['server_url']
326 return self.extras['server_url']
323
327
324 @property
328 @property
325 def request(self):
329 def request(self):
326 return self.extras.get('request') or self.get_request()
330 return self.extras.get('request') or self.get_request()
327
331
328
332
329 class RepoPrePullEvent(RepoVCSEvent):
333 class RepoPrePullEvent(RepoVCSEvent):
330 """
334 """
331 An instance of this class is emitted as an :term:`event` before commits
335 An instance of this class is emitted as an :term:`event` before commits
332 are pulled from a repo.
336 are pulled from a repo.
333 """
337 """
334 name = 'repo-pre-pull'
338 name = 'repo-pre-pull'
335 display_name = lazy_ugettext('repository pre pull')
339 display_name = lazy_ugettext('repository pre pull')
336 description = lazy_ugettext('Event triggered before repository code is pulled')
340 description = lazy_ugettext('Event triggered before repository code is pulled')
337
341
338
342
339 class RepoPullEvent(RepoVCSEvent):
343 class RepoPullEvent(RepoVCSEvent):
340 """
344 """
341 An instance of this class is emitted as an :term:`event` after commits
345 An instance of this class is emitted as an :term:`event` after commits
342 are pulled from a repo.
346 are pulled from a repo.
343 """
347 """
344 name = 'repo-pull'
348 name = 'repo-pull'
345 display_name = lazy_ugettext('repository pull')
349 display_name = lazy_ugettext('repository pull')
346 description = lazy_ugettext('Event triggered after repository code was pulled')
350 description = lazy_ugettext('Event triggered after repository code was pulled')
347
351
348
352
349 class RepoPrePushEvent(RepoVCSEvent):
353 class RepoPrePushEvent(RepoVCSEvent):
350 """
354 """
351 An instance of this class is emitted as an :term:`event` before commits
355 An instance of this class is emitted as an :term:`event` before commits
352 are pushed to a repo.
356 are pushed to a repo.
353 """
357 """
354 name = 'repo-pre-push'
358 name = 'repo-pre-push'
355 display_name = lazy_ugettext('repository pre push')
359 display_name = lazy_ugettext('repository pre push')
356 description = lazy_ugettext('Event triggered before the code is '
360 description = lazy_ugettext('Event triggered before the code is '
357 'pushed to a repository')
361 'pushed to a repository')
358
362
359
363
360 class RepoPushEvent(RepoVCSEvent):
364 class RepoPushEvent(RepoVCSEvent):
361 """
365 """
362 An instance of this class is emitted as an :term:`event` after commits
366 An instance of this class is emitted as an :term:`event` after commits
363 are pushed to a repo.
367 are pushed to a repo.
364
368
365 :param extras: (optional) dict of data from proxied VCS actions
369 :param extras: (optional) dict of data from proxied VCS actions
366 """
370 """
367 name = 'repo-push'
371 name = 'repo-push'
368 display_name = lazy_ugettext('repository push')
372 display_name = lazy_ugettext('repository push')
369 description = lazy_ugettext('Event triggered after the code was '
373 description = lazy_ugettext('Event triggered after the code was '
370 'pushed to a repository')
374 'pushed to a repository')
371
375
372 def __init__(self, repo_name, pushed_commit_ids, extras):
376 def __init__(self, repo_name, pushed_commit_ids, extras):
373 super().__init__(repo_name, extras)
377 super().__init__(repo_name, extras)
374 self.pushed_commit_ids = pushed_commit_ids
378 self.pushed_commit_ids = pushed_commit_ids
375 self.new_refs = extras.new_refs
379 self.new_refs = extras.new_refs
376
380
377 def as_dict(self):
381 def as_dict(self):
378 data = super().as_dict()
382 data = super().as_dict()
379
383
380 def branch_url(branch_name):
384 def branch_url(branch_name):
381 return '{}/changelog?branch={}'.format(
385 return '{}/changelog?branch={}'.format(
382 data['repo']['url'], branch_name)
386 data['repo']['url'], branch_name)
383
387
384 def tag_url(tag_name):
388 def tag_url(tag_name):
385 return '{}/files/{}/'.format(
389 return '{}/files/{}/'.format(
386 data['repo']['url'], tag_name)
390 data['repo']['url'], tag_name)
387
391
388 commits = _commits_as_dict(
392 commits = _commits_as_dict(
389 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
393 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
390
394
391 last_branch = None
395 last_branch = None
392 for commit in reversed(commits):
396 for commit in reversed(commits):
393 commit['branch'] = commit['branch'] or last_branch
397 commit['branch'] = commit['branch'] or last_branch
394 last_branch = commit['branch']
398 last_branch = commit['branch']
395 issues = _issues_as_dict(commits)
399 issues = _issues_as_dict(commits)
396
400
397 branches = set()
401 branches = set()
398 tags = set()
402 tags = set()
399 for commit in commits:
403 for commit in commits:
400 if commit['refs']['tags']:
404 if commit['refs']['tags']:
401 for tag in commit['refs']['tags']:
405 for tag in commit['refs']['tags']:
402 tags.add(tag)
406 tags.add(tag)
403 if commit['branch']:
407 if commit['branch']:
404 branches.add(commit['branch'])
408 branches.add(commit['branch'])
405
409
406 # maybe we have branches in new_refs ?
410 # maybe we have branches in new_refs ?
407 try:
411 try:
408 branches = branches.union(set(self.new_refs['branches']))
412 branches = branches.union(set(self.new_refs['branches']))
409 except Exception:
413 except Exception:
410 pass
414 pass
411
415
412 branches = [
416 branches = [
413 {
417 {
414 'name': branch,
418 'name': branch,
415 'url': branch_url(branch)
419 'url': branch_url(branch)
416 }
420 }
417 for branch in branches
421 for branch in branches
418 ]
422 ]
419
423
420 # maybe we have branches in new_refs ?
424 # maybe we have branches in new_refs ?
421 try:
425 try:
422 tags = tags.union(set(self.new_refs['tags']))
426 tags = tags.union(set(self.new_refs['tags']))
423 except Exception:
427 except Exception:
424 pass
428 pass
425
429
426 tags = [
430 tags = [
427 {
431 {
428 'name': tag,
432 'name': tag,
429 'url': tag_url(tag)
433 'url': tag_url(tag)
430 }
434 }
431 for tag in tags
435 for tag in tags
432 ]
436 ]
433
437
434 data['push'] = {
438 data['push'] = {
435 'commits': commits,
439 'commits': commits,
436 'issues': issues,
440 'issues': issues,
437 'branches': branches,
441 'branches': branches,
438 'tags': tags,
442 'tags': tags,
439 }
443 }
440 return data
444 return data
@@ -1,93 +1,93 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import socket
19 import socket
20 import logging
20 import logging
21
21
22 import rhodecode
22 import rhodecode
23 from zope.cachedescriptors.property import Lazy as LazyProperty
23 from zope.cachedescriptors.property import Lazy as LazyProperty
24 from rhodecode.lib.celerylib.loader import (
24 from rhodecode.lib.celerylib.loader import (
25 celery_app, RequestContextTask, get_logger)
25 celery_app, RequestContextTask, get_logger)
26 from rhodecode.lib.statsd_client import StatsdClient
26 from rhodecode.lib.statsd_client import StatsdClient
27
27
28 async_task = celery_app.task
28 async_task = celery_app.task
29
29
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class ResultWrapper(object):
34 class ResultWrapper(object):
35 def __init__(self, task):
35 def __init__(self, task):
36 self.task = task
36 self.task = task
37
37
38 @LazyProperty
38 @LazyProperty
39 def result(self):
39 def result(self):
40 return self.task
40 return self.task
41
41
42
42
43 def run_task(task, *args, **kwargs):
43 def run_task(task, *args, **kwargs):
44 import celery
44 import celery
45 log.debug('Got task `%s` for execution, celery mode enabled:%s', task, rhodecode.CELERY_ENABLED)
45 log.debug('Got task `%s` for execution, celery mode enabled:%s', task, rhodecode.CELERY_ENABLED)
46 if task is None:
46 if task is None:
47 raise ValueError('Got non-existing task for execution')
47 raise ValueError(f'Got non-existing task: {task} for execution')
48
48
49 exec_mode = 'sync'
49 exec_mode = 'sync'
50 allow_async = True
50 allow_async = True
51
51
52 # if we're already in a celery task, don't allow async execution again
52 # if we're already in a celery task, don't allow async execution again
53 # e.g task within task
53 # e.g task within task
54 in_task = celery.current_task
54 in_task = celery.current_task
55 if in_task:
55 if in_task:
56 log.debug('This task in in context of another task: %s, not allowing another async execution', in_task)
56 log.debug('This task in in context of another task: %s, not allowing another async execution', in_task)
57 allow_async = False
57 allow_async = False
58 if kwargs.pop('allow_subtask', False):
58 if kwargs.pop('allow_subtask', False):
59 log.debug('Forced async by allow_async=True flag')
59 log.debug('Forced async by allow_async=True flag')
60 allow_async = True
60 allow_async = True
61
61
62 t = None
62 t = None
63 if rhodecode.CELERY_ENABLED and allow_async:
63 if rhodecode.CELERY_ENABLED and allow_async:
64
64
65 try:
65 try:
66 t = task.apply_async(args=args, kwargs=kwargs)
66 t = task.apply_async(args=args, kwargs=kwargs)
67 log.debug('executing task %s:%s in async mode', t.task_id, task)
67 log.debug('executing task %s:%s in async mode', t.task_id, task)
68 except socket.error as e:
68 except socket.error as e:
69 if isinstance(e, IOError) and e.errno == 111:
69 if isinstance(e, IOError) and e.errno == 111:
70 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
70 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
71 else:
71 else:
72 log.exception("Exception while connecting to celeryd.")
72 log.exception("Exception while connecting to celeryd.")
73 except KeyError as e:
73 except KeyError as e:
74 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
74 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
75 except Exception as e:
75 except Exception as e:
76 log.exception(
76 log.exception(
77 "Exception while trying to run task asynchronous. "
77 "Exception while trying to run task asynchronous. "
78 "Fallback to sync execution.")
78 "Fallback to sync execution.")
79
79
80 else:
80 else:
81 log.debug('executing task %s:%s in sync mode', 'TASK', task)
81 log.debug('executing task %s:%s in sync mode', 'TASK', task)
82 statsd = StatsdClient.statsd
82 statsd = StatsdClient.statsd
83 if statsd:
83 if statsd:
84 task_repr = getattr(task, 'name', task)
84 task_repr = getattr(task, 'name', task)
85 statsd.incr('rhodecode_celery_task_total', tags=[
85 statsd.incr('rhodecode_celery_task_total', tags=[
86 f'task:{task_repr}',
86 f'task:{task_repr}',
87 'mode:sync'
87 'mode:sync'
88 ])
88 ])
89
89
90 # we got async task, return it after statsd call
90 # we got async task, return it after statsd call
91 if t:
91 if t:
92 return t
92 return t
93 return ResultWrapper(task(*args, **kwargs))
93 return ResultWrapper(task(*args, **kwargs))
@@ -1,173 +1,181 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import sqlalchemy
20 import sqlalchemy
21 from sqlalchemy import UnicodeText
21 from sqlalchemy import UnicodeText
22 from sqlalchemy.ext.mutable import Mutable, \
22 from sqlalchemy.ext.mutable import Mutable, MutableList, MutableDict
23 MutableList as MutationList, \
24 MutableDict as MutationDict
25
23
26 from rhodecode.lib import ext_json
24 from rhodecode.lib import ext_json
27
25
28
26
29 class JsonRaw(str):
27 class JsonRaw(str):
30 """
28 """
31 Allows interacting with a JSON types field using a raw string.
29 Allows interacting with a JSON types field using a raw string.
32
30
33 For example::
31 For example:
34 db_instance = JsonTable()
32 db_instance = JsonTable()
35 db_instance.enabled = True
33 db_instance.enabled = True
36 db_instance.json_data = JsonRaw('{"a": 4}')
34 db_instance.json_data = JsonRaw('{"a": 4}')
37
35
38 This will bypass serialization/checks, and allow storing
36 This will bypass serialization/checks, and allow storing
39 raw values
37 raw values
40 """
38 """
41 pass
39 pass
42
40
43
41
44 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
42 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
45 """
43 """
46 Represents an immutable structure as a json-encoded string.
44 Represents an immutable structure as a json-encoded string.
47
45
48 If default is, for example, a dict, then a NULL value in the
46 If default is, for example, a dict, then a NULL value in the
49 database will be exposed as an empty dict.
47 database will be exposed as an empty dict.
50 """
48 """
51
49
52 impl = UnicodeText
50 impl = UnicodeText
53 safe = True
51 safe = True
54 enforce_str = True
52 enforce_str = True
55
53
56 def __init__(self, *args, **kwargs):
54 def __init__(self, *args, **kwargs):
57 self.default = kwargs.pop('default', None)
55 self.default = kwargs.pop('default', None)
58 self.safe = kwargs.pop('safe_json', self.safe)
56 self.safe = kwargs.pop('safe_json', self.safe)
59 self.enforce_str = kwargs.pop('enforce_str', self.enforce_str)
57 self.enforce_str = kwargs.pop('enforce_str', self.enforce_str)
60 self.dialect_map = kwargs.pop('dialect_map', {})
58 self.dialect_map = kwargs.pop('dialect_map', {})
61 super(JSONEncodedObj, self).__init__(*args, **kwargs)
59 super(JSONEncodedObj, self).__init__(*args, **kwargs)
62
60
63 def load_dialect_impl(self, dialect):
61 def load_dialect_impl(self, dialect):
64 if dialect.name in self.dialect_map:
62 if dialect.name in self.dialect_map:
65 return dialect.type_descriptor(self.dialect_map[dialect.name])
63 return dialect.type_descriptor(self.dialect_map[dialect.name])
66 return dialect.type_descriptor(self.impl)
64 return dialect.type_descriptor(self.impl)
67
65
68 def process_bind_param(self, value, dialect):
66 def process_bind_param(self, value, dialect):
69 if isinstance(value, JsonRaw):
67 if isinstance(value, JsonRaw):
70 value = value
68 value = value
71 elif value is not None:
69 elif value is not None:
72 if self.enforce_str:
70 if self.enforce_str:
73 value = ext_json.str_json(value)
71 value = ext_json.str_json(value)
74 else:
72 else:
75 value = ext_json.json.dumps(value)
73 value = ext_json.json.dumps(value)
76 return value
74 return value
77
75
78 def process_result_value(self, value, dialect):
76 def process_result_value(self, value, dialect):
79 if self.default is not None and (not value or value == '""'):
77 if self.default is not None and (not value or value == '""'):
80 return self.default()
78 return self.default()
81
79
82 if value is not None:
80 if value is not None:
83 try:
81 try:
84 value = ext_json.json.loads(value)
82 value = ext_json.json.loads(value)
85 except Exception:
83 except Exception:
86 if self.safe and self.default is not None:
84 if self.safe and self.default is not None:
87 return self.default()
85 return self.default()
88 else:
86 else:
89 raise
87 raise
90 return value
88 return value
91
89
92
90
93 class MutationObj(Mutable):
91 class MutationObj(Mutable):
94
92
95 @classmethod
93 @classmethod
96 def coerce(cls, key, value):
94 def coerce(cls, key, value):
97 if isinstance(value, dict) and not isinstance(value, MutationDict):
95 if isinstance(value, dict) and not isinstance(value, MutationDict):
98 return MutationDict.coerce(key, value)
96 return MutationDict.coerce(key, value)
99 if isinstance(value, list) and not isinstance(value, MutationList):
97 if isinstance(value, list) and not isinstance(value, MutationList):
100 return MutationList.coerce(key, value)
98 return MutationList.coerce(key, value)
101 return value
99 return value
102
100
103 def de_coerce(self):
101 def de_coerce(self) -> "MutationObj":
104 return self
102 return self
105
103
106 @classmethod
104 @classmethod
107 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
105 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
108 key = attribute.key
106 key = attribute.key
109 if parent_cls is not attribute.class_:
107 if parent_cls is not attribute.class_:
110 return
108 return
111
109
112 # rely on "propagate" here
110 # rely on "propagate" here
113 parent_cls = attribute.class_
111 parent_cls = attribute.class_
114
112
115 def load(state, *args):
113 def load(state, *args):
116 val = state.dict.get(key, None)
114 val = state.dict.get(key, None)
117 if coerce:
115 if coerce:
118 val = cls.coerce(key, val)
116 val = cls.coerce(key, val)
119 state.dict[key] = val
117 state.dict[key] = val
120 if isinstance(val, cls):
118 if isinstance(val, cls):
121 val._parents[state.obj()] = key
119 val._parents[state.obj()] = key
122
120
123 def set(target, value, oldvalue, initiator):
121 def set(target, value, oldvalue, initiator):
124 if not isinstance(value, cls):
122 if not isinstance(value, cls):
125 value = cls.coerce(key, value)
123 value = cls.coerce(key, value)
126 if isinstance(value, cls):
124 if isinstance(value, cls):
127 value._parents[target.obj()] = key
125 value._parents[target.obj()] = key
128 if isinstance(oldvalue, cls):
126 if isinstance(oldvalue, cls):
129 oldvalue._parents.pop(target.obj(), None)
127 oldvalue._parents.pop(target.obj(), None)
130 return value
128 return value
131
129
132 def pickle(state, state_dict):
130 def pickle(state, state_dict):
133 val = state.dict.get(key, None)
131 val = state.dict.get(key, None)
134 if isinstance(val, cls):
132 if isinstance(val, cls):
135 if 'ext.mutable.values' not in state_dict:
133 if 'ext.mutable.values' not in state_dict:
136 state_dict['ext.mutable.values'] = []
134 state_dict['ext.mutable.values'] = []
137 state_dict['ext.mutable.values'].append(val)
135 state_dict['ext.mutable.values'].append(val)
138
136
139 def unpickle(state, state_dict):
137 def unpickle(state, state_dict):
140 if 'ext.mutable.values' in state_dict:
138 if 'ext.mutable.values' in state_dict:
141 for val in state_dict['ext.mutable.values']:
139 for val in state_dict['ext.mutable.values']:
142 val._parents[state.obj()] = key
140 val._parents[state.obj()] = key
143
141
144 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
142 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
145 propagate=True)
143 propagate=True)
146 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
144 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
147 propagate=True)
145 propagate=True)
148 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
146 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
149 propagate=True)
147 propagate=True)
150 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
148 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
151 propagate=True)
149 propagate=True)
152 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
150 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
153 propagate=True)
151 propagate=True)
154
152
155
153
154 class MutationList(MutableList):
155 def de_coerce(self):
156 return list(self)
157
158
159 class MutationDict(MutableDict):
160 def de_coerce(self):
161 return dict(self)
162
163
156 def JsonType(impl=None, **kwargs):
164 def JsonType(impl=None, **kwargs):
157 """
165 """
158 Helper for using a mutation obj, it allows to use .with_variant easily.
166 Helper for using a mutation obj, it allows to use .with_variant easily.
159 example::
167 example::
160
168
161 settings = Column('settings_json',
169 settings = Column('settings_json',
162 MutationObj.as_mutable(
170 MutationObj.as_mutable(
163 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
171 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
164 """
172 """
165
173
166 if impl == 'list':
174 if impl == 'list':
167 return JSONEncodedObj(default=list, **kwargs)
175 return JSONEncodedObj(default=list, **kwargs)
168 elif impl == 'dict':
176 elif impl == 'dict':
169 return JSONEncodedObj(default=dict, **kwargs)
177 return JSONEncodedObj(default=dict, **kwargs)
170 else:
178 else:
171 return JSONEncodedObj(**kwargs)
179 return JSONEncodedObj(**kwargs)
172
180
173
181
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,159 +1,160 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import re
19 import re
20 import logging
20 import logging
21
21
22 import ipaddress
22 import ipaddress
23 import colander
23 import colander
24
24
25 from rhodecode.translation import _
25 from rhodecode.translation import _
26 from rhodecode.lib.utils2 import glob2re
26 from rhodecode.lib.utils2 import glob2re
27 from rhodecode.lib.str_utils import safe_str
27 from rhodecode.lib.str_utils import safe_str
28 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.ext_json import json, sjson
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 def ip_addr_validator(node, value):
33 def ip_addr_validator(node, value):
34 try:
34 try:
35 # this raises an ValueError if address is not IpV4 or IpV6
35 # this raises an ValueError if address is not IpV4 or IpV6
36 ipaddress.ip_network(safe_str(value), strict=False)
36 ipaddress.ip_network(safe_str(value), strict=False)
37 except ValueError:
37 except ValueError:
38 msg = _('Please enter a valid IPv4 or IpV6 address')
38 msg = _('Please enter a valid IPv4 or IpV6 address')
39 raise colander.Invalid(node, msg)
39 raise colander.Invalid(node, msg)
40
40
41
41
42 class IpAddrValidator(object):
42 class IpAddrValidator(object):
43 def __init__(self, strict=True):
43 def __init__(self, strict=True):
44 self.strict = strict
44 self.strict = strict
45
45
46 def __call__(self, node, value):
46 def __call__(self, node, value):
47 try:
47 try:
48 # this raises an ValueError if address is not IpV4 or IpV6
48 # this raises an ValueError if address is not IpV4 or IpV6
49 ipaddress.ip_network(safe_str(value), strict=self.strict)
49 ipaddress.ip_network(safe_str(value), strict=self.strict)
50 except ValueError:
50 except ValueError:
51 msg = _('Please enter a valid IPv4 or IpV6 address')
51 msg = _('Please enter a valid IPv4 or IpV6 address')
52 raise colander.Invalid(node, msg)
52 raise colander.Invalid(node, msg)
53
53
54
54
55 def glob_validator(node, value):
55 def glob_validator(node, value):
56 try:
56 try:
57 re.compile('^' + glob2re(value) + '$')
57 re.compile('^' + glob2re(value) + '$')
58 except Exception:
58 except Exception:
59 msg = _('Invalid glob pattern')
59 msg = _('Invalid glob pattern')
60 raise colander.Invalid(node, msg)
60 raise colander.Invalid(node, msg)
61
61
62
62
63 def valid_name_validator(node, value):
63 def valid_name_validator(node, value):
64 from rhodecode.model.validation_schema import types
64 from rhodecode.model.validation_schema import types
65 if value is types.RootLocation:
65 if value is types.RootLocation:
66 return
66 return
67
67
68 msg = _('Name must start with a letter or number. Got `{}`').format(value)
68 msg = _('Name must start with a letter or number. Got `{}`').format(value)
69 if not re.match(r'^[a-zA-z0-9]{1,}', value):
69 if not re.match(r'^[a-zA-z0-9]{1,}', value):
70 raise colander.Invalid(node, msg)
70 raise colander.Invalid(node, msg)
71
71
72
72
73 class InvalidCloneUrl(Exception):
73 class InvalidCloneUrl(Exception):
74 allowed_prefixes = ()
74 allowed_prefixes = ()
75
75
76
76
77 def url_validator(url, repo_type, config):
77 def url_validator(url, repo_type, config):
78 from rhodecode.lib.vcs.backends.hg import MercurialRepository
78 from rhodecode.lib.vcs.backends.hg import MercurialRepository
79 from rhodecode.lib.vcs.backends.git import GitRepository
79 from rhodecode.lib.vcs.backends.git import GitRepository
80 from rhodecode.lib.vcs.backends.svn import SubversionRepository
80 from rhodecode.lib.vcs.backends.svn import SubversionRepository
81
81
82 if repo_type == 'hg':
82 if repo_type == 'hg':
83 allowed_prefixes = ('http', 'svn+http', 'git+http')
83 allowed_prefixes = ('http', 'svn+http', 'git+http')
84
84
85 if 'http' in url[:4]:
85 if 'http' in url[:4]:
86 # initially check if it's at least the proper URL
86 # initially check if it's at least the proper URL
87 # or does it pass basic auth
87 # or does it pass basic auth
88
88
89 return MercurialRepository.check_url(url, config)
89 return MercurialRepository.check_url(url, config)
90 elif 'svn+http' in url[:8]: # svn->hg import
90 elif 'svn+http' in url[:8]: # svn->hg import
91 SubversionRepository.check_url(url, config)
91 SubversionRepository.check_url(url, config)
92 elif 'git+http' in url[:8]: # git->hg import
92 elif 'git+http' in url[:8]: # git->hg import
93 raise NotImplementedError()
93 raise NotImplementedError()
94 else:
94 else:
95 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
95 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
96 'Allowed url must start with one of %s'
96 'Allowed url must start with one of %s'
97 % (url, ','.join(allowed_prefixes)))
97 % (url, ','.join(allowed_prefixes)))
98 exc.allowed_prefixes = allowed_prefixes
98 exc.allowed_prefixes = allowed_prefixes
99 raise exc
99 raise exc
100
100
101 elif repo_type == 'git':
101 elif repo_type == 'git':
102 allowed_prefixes = ('http', 'svn+http', 'hg+http')
102 allowed_prefixes = ('http', 'svn+http', 'hg+http')
103 if 'http' in url[:4]:
103 if 'http' in url[:4]:
104 # initially check if it's at least the proper URL
104 # initially check if it's at least the proper URL
105 # or does it pass basic auth
105 # or does it pass basic auth
106 return GitRepository.check_url(url, config)
106 return GitRepository.check_url(url, config)
107 elif 'svn+http' in url[:8]: # svn->git import
107 elif 'svn+http' in url[:8]: # svn->git import
108 raise NotImplementedError()
108 raise NotImplementedError()
109 elif 'hg+http' in url[:8]: # hg->git import
109 elif 'hg+http' in url[:8]: # hg->git import
110 raise NotImplementedError()
110 raise NotImplementedError()
111 else:
111 else:
112 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
112 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
113 'Allowed url must start with one of %s'
113 'Allowed url must start with one of %s'
114 % (url, ','.join(allowed_prefixes)))
114 % (url, ','.join(allowed_prefixes)))
115 exc.allowed_prefixes = allowed_prefixes
115 exc.allowed_prefixes = allowed_prefixes
116 raise exc
116 raise exc
117 elif repo_type == 'svn':
117 elif repo_type == 'svn':
118 # no validation for SVN yet
118 # no validation for SVN yet
119 return
119 return
120
120
121 raise InvalidCloneUrl(f'Invalid repo type specified: `{repo_type}`')
121 raise InvalidCloneUrl(f'Invalid repo type specified: `{repo_type}`')
122
122
123
123
124 class CloneUriValidator(object):
124 class CloneUriValidator(object):
125 def __init__(self, repo_type):
125 def __init__(self, repo_type):
126 self.repo_type = repo_type
126 self.repo_type = repo_type
127
127
128 def __call__(self, node, value):
128 def __call__(self, node, value):
129
129
130 from rhodecode.lib.utils import make_db_config
130 from rhodecode.lib.utils import make_db_config
131 try:
131 try:
132 config = make_db_config(clear_session=False)
132 config = make_db_config(clear_session=False)
133 url_validator(value, self.repo_type, config)
133 url_validator(value, self.repo_type, config)
134 except InvalidCloneUrl as e:
134 except InvalidCloneUrl as e:
135 log.warning(e)
135 log.warning(e)
136 raise colander.Invalid(node, str(e))
136 raise colander.Invalid(node, str(e))
137 except Exception as e:
137 except Exception as e:
138 log.exception('Url validation failed')
138 log.exception('Url validation failed')
139 reason = repr(e)
139 reason = repr(e)
140 reason = reason.replace('<', '&lt;').replace('>', '&gt;')
140 reason = reason.replace('<', '&lt;').replace('>', '&gt;')
141 msg = _('invalid clone url or credentials for {repo_type} repository. Reason: {reason}')\
141 msg = _('invalid clone url or credentials for {repo_type} repository. Reason: {reason}')\
142 .format(reason=reason, repo_type=self.repo_type)
142 .format(reason=reason, repo_type=self.repo_type)
143 raise colander.Invalid(node, msg)
143 raise colander.Invalid(node, msg)
144
144
145
145
146 def json_validator(node, value):
146 def json_validator(node, value):
147 try:
147 try:
148 json.loads(value)
148 json.loads(value)
149 except (Exception,):
149 except (Exception,):
150 msg = _('Please enter a valid json object')
150 msg = _('Please enter a valid json object')
151 raise colander.Invalid(node, msg)
151 raise colander.Invalid(node, msg)
152
152
153
153
154 def json_validator_with_exc(node, value):
154 def json_validator_with_exc(node, value):
155
155 try:
156 try:
156 json.loads(value)
157 json.loads(value)
157 except (Exception,) as e:
158 except (Exception,) as e:
158 msg = _(f'Please enter a valid json object: `{e}`')
159 msg = _(f'Please enter a valid json object type={type(value)}: `{e}`')
159 raise colander.Invalid(node, msg)
160 raise colander.Invalid(node, msg)
@@ -1,412 +1,412 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('admin_artifacts', '/_admin/artifacts', []);
15 pyroutes.register('admin_artifacts', '/_admin/artifacts', []);
16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
23 pyroutes.register('admin_automation', '/_admin/automation', []);
24 pyroutes.register('admin_automation_update', '/_admin/automation/%(entry_id)s/update', ['entry_id']);
23 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
25 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
24 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
26 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
25 pyroutes.register('admin_home', '/_admin', []);
27 pyroutes.register('admin_home', '/_admin', []);
26 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
28 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
27 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
29 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
28 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
30 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
29 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
31 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
30 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
32 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
31 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
33 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
32 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
34 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
33 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
35 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
34 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
36 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
35 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
37 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
36 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
38 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
37 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
39 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
38 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
40 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
39 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
41 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
42 pyroutes.register('admin_scheduler', '/_admin/scheduler', []);
43 pyroutes.register('admin_scheduler_show_tasks', '/_admin/scheduler/_tasks', []);
40 pyroutes.register('admin_settings', '/_admin/settings', []);
44 pyroutes.register('admin_settings', '/_admin/settings', []);
41 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
42 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
43 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
45 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
44 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
46 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
45 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
47 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
46 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
48 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
47 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
48 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
49 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
51 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
50 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
52 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
51 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
53 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
52 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
54 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
53 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
55 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
54 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
56 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
55 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
57 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
56 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
58 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
57 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
59 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
58 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
60 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
59 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
61 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
60 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
62 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
61 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
63 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
62 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
64 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
63 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
65 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
64 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
66 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
65 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
67 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
66 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
68 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
67 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
69 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
68 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
70 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
69 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
71 pyroutes.register('admin_settings_scheduler_create', '/_admin/scheduler/create', []);
70 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
72 pyroutes.register('admin_settings_scheduler_delete', '/_admin/scheduler/%(schedule_id)s/delete', ['schedule_id']);
71 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
73 pyroutes.register('admin_settings_scheduler_edit', '/_admin/scheduler/%(schedule_id)s', ['schedule_id']);
72 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
74 pyroutes.register('admin_settings_scheduler_execute', '/_admin/scheduler/%(schedule_id)s/execute', ['schedule_id']);
73 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
75 pyroutes.register('admin_settings_scheduler_new', '/_admin/scheduler/new', []);
74 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
76 pyroutes.register('admin_settings_scheduler_update', '/_admin/scheduler/%(schedule_id)s/update', ['schedule_id']);
75 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
76 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
77 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
77 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
78 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
78 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
79 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
79 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
80 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
80 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
81 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
81 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
82 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
82 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
83 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
83 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
84 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
84 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
85 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
85 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
86 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
86 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
87 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
87 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
88 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
88 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
89 pyroutes.register('apiv2', '/_admin/api', []);
89 pyroutes.register('apiv2', '/_admin/api', []);
90 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
90 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
91 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
91 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
92 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
92 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
93 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
93 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
94 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
94 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
95 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
95 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
96 pyroutes.register('channelstream_proxy', '/_channelstream', []);
96 pyroutes.register('channelstream_proxy', '/_channelstream', []);
97 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
97 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
98 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
98 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
99 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
99 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
100 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
100 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
101 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
101 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
102 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
102 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
103 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
103 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
104 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
104 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
105 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
105 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
106 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
106 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
107 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
107 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
108 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
108 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
109 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
109 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
110 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
110 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
111 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
111 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
113 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
113 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
114 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
114 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
115 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
115 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
116 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
116 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
117 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
117 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
118 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
118 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
119 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
119 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
120 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
120 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
121 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
121 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
122 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
122 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
123 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
123 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
124 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
124 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
125 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
125 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
126 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
126 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
127 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
127 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
128 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
128 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
129 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
129 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
130 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
130 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
131 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
131 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
132 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
132 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
133 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
133 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
134 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
134 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
135 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
135 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
136 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
136 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
137 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
137 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
138 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
138 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
139 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
139 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
140 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
140 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
141 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
141 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
142 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
142 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
143 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
143 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
144 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
144 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
145 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
145 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
146 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
146 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
147 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
147 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
148 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
148 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
149 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
149 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
150 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
150 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
151 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
151 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
152 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
152 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
153 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
153 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
154 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
154 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
155 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
155 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
156 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
156 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
157 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
157 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
158 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
158 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
159 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
159 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
160 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
160 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
161 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
161 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
162 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
162 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
163 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
163 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
164 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
164 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
165 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
165 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
166 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
166 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
167 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
167 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
168 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
168 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
169 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
169 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
170 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
170 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
171 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
171 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
172 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
172 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
173 pyroutes.register('favicon', '/favicon.ico', []);
173 pyroutes.register('favicon', '/favicon.ico', []);
174 pyroutes.register('file_preview', '/_file_preview', []);
174 pyroutes.register('file_preview', '/_file_preview', []);
175 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
175 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
176 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
176 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
177 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
177 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
178 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
178 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
179 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
179 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
180 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
180 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
181 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
181 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
182 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
182 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
183 pyroutes.register('gists_create', '/_admin/gists/create', []);
183 pyroutes.register('gists_create', '/_admin/gists/create', []);
184 pyroutes.register('gists_new', '/_admin/gists/new', []);
184 pyroutes.register('gists_new', '/_admin/gists/new', []);
185 pyroutes.register('gists_show', '/_admin/gists', []);
185 pyroutes.register('gists_show', '/_admin/gists', []);
186 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
186 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
187 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
187 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
188 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
188 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
189 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
189 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
190 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
190 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
191 pyroutes.register('goto_switcher_data', '/_goto_data', []);
191 pyroutes.register('goto_switcher_data', '/_goto_data', []);
192 pyroutes.register('home', '/', []);
192 pyroutes.register('home', '/', []);
193 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
193 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
194 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
194 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
195 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
195 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
196 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
196 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
197 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
197 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
198 pyroutes.register('journal', '/_admin/journal', []);
198 pyroutes.register('journal', '/_admin/journal', []);
199 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
199 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
200 pyroutes.register('journal_public', '/_admin/public_journal', []);
200 pyroutes.register('journal_public', '/_admin/public_journal', []);
201 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
201 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
202 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
202 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
203 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
203 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
204 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
204 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
205 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
205 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
206 pyroutes.register('login', '/_admin/login', []);
206 pyroutes.register('login', '/_admin/login', []);
207 pyroutes.register('logout', '/_admin/logout', []);
207 pyroutes.register('logout', '/_admin/logout', []);
208 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
208 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
209 pyroutes.register('main_page_repos_data', '/_home_repos', []);
209 pyroutes.register('main_page_repos_data', '/_home_repos', []);
210 pyroutes.register('markup_preview', '/_markup_preview', []);
210 pyroutes.register('markup_preview', '/_markup_preview', []);
211 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
211 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
212 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
212 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
213 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
213 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
214 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
214 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
215 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
215 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
216 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
216 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
218 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
218 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
219 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
219 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
220 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
220 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
221 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
221 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
222 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
222 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
223 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
223 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
224 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
224 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
225 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
225 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
226 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
226 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
227 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
227 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
228 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
228 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
229 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
229 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
230 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
230 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
231 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
231 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
232 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
232 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
233 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
233 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
234 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
234 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
235 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
235 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
236 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
236 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
237 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
237 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
238 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
238 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
239 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
239 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
240 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
240 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
241 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
241 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
242 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
242 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
243 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
243 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
244 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
244 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
245 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
245 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
246 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
246 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
247 pyroutes.register('ops_healthcheck', '/_admin/ops/status', []);
247 pyroutes.register('ops_healthcheck', '/_admin/ops/status', []);
248 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
248 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
249 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
249 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
250 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
250 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
251 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
251 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
252 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
252 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
253 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
253 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
254 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
254 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
255 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
255 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
256 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
256 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
257 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
257 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
258 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
258 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
259 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
259 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
260 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
260 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
261 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
261 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
262 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
262 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
263 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
263 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
264 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
264 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
265 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
265 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
266 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
266 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
267 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
267 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
268 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
268 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
269 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
269 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
270 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
270 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
271 pyroutes.register('register', '/_admin/register', []);
271 pyroutes.register('register', '/_admin/register', []);
272 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
272 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
273 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
273 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
274 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
274 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
275 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
275 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
276 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
276 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
277 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
277 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
278 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
278 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
279 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
279 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
280 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
280 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
281 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
281 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
282 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
282 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
283 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
283 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
284 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
284 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
285 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
285 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
286 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
286 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
287 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
287 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
288 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
288 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
289 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
289 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
290 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
290 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
291 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
291 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
292 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
292 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
293 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/history_view/%(comment_history_id)s', ['repo_name', 'commit_id', 'comment_id', 'comment_history_id']);
293 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/history_view/%(comment_history_id)s', ['repo_name', 'commit_id', 'comment_id', 'comment_history_id']);
294 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
294 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
295 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
295 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
296 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
296 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
297 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
297 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
298 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
298 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
299 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
299 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
300 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
300 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
301 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
301 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
302 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
302 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
303 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
303 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
304 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
304 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
305 pyroutes.register('repo_compare', '/%(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']);
305 pyroutes.register('repo_compare', '/%(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']);
306 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
306 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
307 pyroutes.register('repo_create', '/_admin/repos/create', []);
307 pyroutes.register('repo_create', '/_admin/repos/create', []);
308 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
308 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
309 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
309 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
310 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
310 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
311 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
312 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
311 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
313 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
312 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
314 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
313 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
314 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
319 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
320 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
319 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
321 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
320 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
322 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
321 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
322 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
325 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
326 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
325 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
327 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
326 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
328 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
327 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
329 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
328 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
330 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
329 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
331 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
330 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
332 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
331 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
333 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
332 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
334 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
333 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
335 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
334 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
336 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
335 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
337 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
336 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
338 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
337 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
339 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
338 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
340 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
339 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
341 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
340 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
342 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
341 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
343 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
342 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
344 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
343 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
345 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
344 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
346 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
345 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
347 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
346 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
348 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
347 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
349 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
348 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
350 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
349 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
351 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
350 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
352 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
351 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
353 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
352 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
354 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
353 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
355 pyroutes.register('repo_list_data', '/_repos', []);
354 pyroutes.register('repo_list_data', '/_repos', []);
356 pyroutes.register('repo_new', '/_admin/repos/new', []);
355 pyroutes.register('repo_new', '/_admin/repos/new', []);
357 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
356 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
358 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
357 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
359 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
358 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
360 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
359 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
361 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
360 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
362 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
361 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
363 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
362 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
364 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
363 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
364 pyroutes.register('repo_settings_quick_actions', '/%(repo_name)s/settings/quick-action', ['repo_name']);
365 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
365 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
366 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
366 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
367 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
367 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
368 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
368 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
369 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
369 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
370 pyroutes.register('repos', '/_admin/repos', []);
370 pyroutes.register('repos', '/_admin/repos', []);
371 pyroutes.register('repos_data', '/_admin/repos_data', []);
371 pyroutes.register('repos_data', '/_admin/repos_data', []);
372 pyroutes.register('reset_password', '/_admin/password_reset', []);
372 pyroutes.register('reset_password', '/_admin/password_reset', []);
373 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
373 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
374 pyroutes.register('robots', '/robots.txt', []);
374 pyroutes.register('robots', '/robots.txt', []);
375 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
375 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
376 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
376 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
377 pyroutes.register('search', '/_admin/search', []);
377 pyroutes.register('search', '/_admin/search', []);
378 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
378 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
379 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
379 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
380 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
380 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
381 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
381 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
382 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
382 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
383 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
383 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
384 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
384 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
385 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
385 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
386 pyroutes.register('upload_file', '/_file_store/upload', []);
386 pyroutes.register('upload_file', '/_file_store/upload', []);
387 pyroutes.register('user_autocomplete_data', '/_users', []);
387 pyroutes.register('user_autocomplete_data', '/_users', []);
388 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
388 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
389 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
389 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
390 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
390 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
391 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
391 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
392 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
392 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
393 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
393 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
394 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
394 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
395 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
395 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
396 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
396 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
397 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
397 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
398 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
398 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
399 pyroutes.register('user_groups', '/_admin/user_groups', []);
399 pyroutes.register('user_groups', '/_admin/user_groups', []);
400 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
400 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
401 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
401 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
402 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
402 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
403 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
403 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
404 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
404 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
405 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
405 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
406 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
406 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
407 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
407 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
408 pyroutes.register('users', '/_admin/users', []);
408 pyroutes.register('users', '/_admin/users', []);
409 pyroutes.register('users_create', '/_admin/users/create', []);
409 pyroutes.register('users_create', '/_admin/users/create', []);
410 pyroutes.register('users_data', '/_admin/users_data', []);
410 pyroutes.register('users_data', '/_admin/users_data', []);
411 pyroutes.register('users_new', '/_admin/users/new', []);
411 pyroutes.register('users_new', '/_admin/users/new', []);
412 }
412 }
@@ -1,38 +1,41 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Artifacts Admin')}
4 ${_('Artifacts Admin')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='artifacts')}
17 ${self.admin_menu(active='artifacts')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="panel panel-default">
24 <div class="panel panel-default">
25 <div class="panel-heading">
25 <div class="panel-heading">
26 <h3 class="panel-title">${_('Artifacts Administration.')}</h3>
26 <h3 class="panel-title">${_('Artifacts Administration.')}</h3>
27 </div>
27 </div>
28 <div class="panel-body">
28 <div class="panel-body">
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
30
30 <p>
31 Artifacts are a binary file storage within RhodeCode that allows asset management next to version control system with fine-grained access control.
32 This functionality allows release builds or other types of binary asset to be stored and managed by RhodeCode.
33 </p>
31 </div>
34 </div>
32 </div>
35 </div>
33
36
34 </div>
37 </div>
35
38
36
39
37 </%def>
40 </%def>
38
41
@@ -1,9 +1,37 b''
1 <div class="panel panel-default">
1 <%inherit file="/base/base.mako"/>
2 <div class="panel-heading">
2
3 <h3 class="panel-title">${_('Admin Automation')}</h3>
3 <%def name="title()">
4 ${_('Artifacts Admin')}
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
8 </%def>
9
10 <%def name="breadcrumbs_links()"></%def>
11
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
14 </%def>
15
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='automation')}
18 </%def>
19
20 <%def name="main()">
21
22 <div class="box">
23
24 <div class="panel panel-default">
25 <div class="panel-heading">
26 <h3 class="panel-title">${_('Automation Administration.')}</h3>
27 </div>
28 <div class="panel-body">
29 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
30 <img alt="admin-automation" style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
31 </div>
4 </div>
32 </div>
5 <div class="panel-body">
33
6 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
7 <img style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
8 </div>
9 </div>
34 </div>
35
36
37 </%def>
@@ -1,147 +1,147 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 %if c.show_private:
4 %if c.show_private:
5 ${_('Private Gists for user {}').format(c.rhodecode_user.username)}
5 ${_('Private Gists for user {}').format(c.rhodecode_user.username)}
6 %elif c.show_public:
6 %elif c.show_public:
7 ${_('Public Gists for user {}').format(c.rhodecode_user.username)}
7 ${_('Public Gists for user {}').format(c.rhodecode_user.username)}
8 %else:
8 %else:
9 ${_('Public Gists')}
9 ${_('Public Gists')}
10 %endif
10 %endif
11 %if c.rhodecode_name:
11 %if c.rhodecode_name:
12 &middot; ${h.branding(c.rhodecode_name)}
12 &middot; ${h.branding(c.rhodecode_name)}
13 %endif
13 %endif
14 </%def>
14 </%def>
15
15
16 <%def name="breadcrumbs_links()"></%def>
16 <%def name="breadcrumbs_links()"></%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='gists')}
19 ${self.menu_items(active='gists')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23
23
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26
26
27 <ul class="button-links">
27 <ul class="button-links">
28 % if c.is_super_admin:
28 % if c.is_super_admin:
29 <li><a class="btn ${h.is_active('all', c.active)}" href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
29 <li><a class="btn ${h.is_active('all', c.active)}" href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
30 %endif
30 %endif
31 <li><a class="btn ${h.is_active('public', c.active)}" href="${h.route_path('gists_show')}">${_('All public')}</a></li>
31 <li><a class="btn ${h.is_active('public', c.active)}" href="${h.route_path('gists_show')}">${_('All public')}</a></li>
32 %if c.rhodecode_user.username != h.DEFAULT_USER:
32 %if c.rhodecode_user.username != h.DEFAULT_USER:
33 <li><a class="btn ${h.is_active('my_all', c.active)}" href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
33 <li><a class="btn ${h.is_active('my_all', c.active)}" href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
34 <li><a class="btn ${h.is_active('my_private', c.active)}" href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
34 <li><a class="btn ${h.is_active('my_private', c.active)}" href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
35 <li><a class="btn ${h.is_active('my_public', c.active)}" href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
35 <li><a class="btn ${h.is_active('my_public', c.active)}" href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
36 %endif
36 %endif
37 </ul>
37 </ul>
38
38
39 % if c.rhodecode_user.username != h.DEFAULT_USER:
39 % if c.rhodecode_user.username != h.DEFAULT_USER:
40 <div class="pull-right">
40 <div class="pull-right">
41 <a class="btn btn-primary" href="${h.route_path('gists_new')}" >
41 <a class="btn btn-primary" href="${h.route_path('gists_new')}" >
42 ${_(u'Create New Gist')}
42 ${_('Create New Gist')}
43 </a>
43 </a>
44 </div>
44 </div>
45 % endif
45 % endif
46
46
47 <div class="grid-quick-filter">
47 <div class="grid-quick-filter">
48 <ul class="grid-filter-box">
48 <ul class="grid-filter-box">
49 <li class="grid-filter-box-icon">
49 <li class="grid-filter-box-icon">
50 <i class="icon-search"></i>
50 <i class="icon-search"></i>
51 </li>
51 </li>
52 <li class="grid-filter-box-input">
52 <li class="grid-filter-box-input">
53 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
53 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
54 </li>
54 </li>
55 </ul>
55 </ul>
56 </div>
56 </div>
57
57
58 </div>
58 </div>
59
59
60 <div class="main-content-full-width">
60 <div class="main-content-full-width">
61 <div id="repos_list_wrap">
61 <div id="repos_list_wrap">
62 <table id="gist_list_table" class="display"></table>
62 <table id="gist_list_table" class="display"></table>
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 </div>
66 </div>
67
67
68 <script type="text/javascript">
68 <script type="text/javascript">
69 $(document).ready(function() {
69 $(document).ready(function() {
70
70
71 var get_datatable_count = function(){
71 var get_datatable_count = function(){
72 var api = $('#gist_list_table').dataTable().api();
72 var api = $('#gist_list_table').dataTable().api();
73 $('#gists_count').text(api.page.info().recordsDisplay);
73 $('#gists_count').text(api.page.info().recordsDisplay);
74 };
74 };
75
75
76
76
77 // custom filter that filters by access_id, description or author
77 // custom filter that filters by access_id, description or author
78 $.fn.dataTable.ext.search.push(
78 $.fn.dataTable.ext.search.push(
79 function( settings, data, dataIndex ) {
79 function( settings, data, dataIndex ) {
80 var query = $('#q_filter').val();
80 var query = $('#q_filter').val();
81 var author = data[0].strip();
81 var author = data[0].strip();
82 var access_id = data[2].strip();
82 var access_id = data[2].strip();
83 var description = data[3].strip();
83 var description = data[3].strip();
84
84
85 var query_str = (access_id + " " + author + " " + description).toLowerCase();
85 var query_str = (access_id + " " + author + " " + description).toLowerCase();
86
86
87 if(query_str.indexOf(query.toLowerCase()) !== -1){
87 if(query_str.indexOf(query.toLowerCase()) !== -1){
88 return true;
88 return true;
89 }
89 }
90 return false;
90 return false;
91 }
91 }
92 );
92 );
93
93
94 // gists list
94 // gists list
95 var gist_data = ${c.data|n};
95 var gist_data = ${c.data|n};
96 $('#gist_list_table').DataTable({
96 $('#gist_list_table').DataTable({
97 data: gist_data,
97 data: gist_data,
98 dom: 'rtp',
98 dom: 'rtp',
99 pageLength: ${c.visual.dashboard_items},
99 pageLength: ${c.visual.dashboard_items},
100 order: [[ 4, "desc" ]],
100 order: [[ 4, "desc" ]],
101 columns: [
101 columns: [
102 { data: {"_": "author",
102 { data: {"_": "author",
103 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
103 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
104 { data: {"_": "type",
104 { data: {"_": "type",
105 "sort": "type"}, title: "${_("Type")}", width: "100px", className: "td-gist-type" },
105 "sort": "type"}, title: "${_("Type")}", width: "100px", className: "td-gist-type" },
106 { data: {"_": "access_id",
106 { data: {"_": "access_id",
107 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
107 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
108 { data: {"_": "description",
108 { data: {"_": "description",
109 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
109 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
110 { data: {"_": "created_on",
110 { data: {"_": "created_on",
111 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
111 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
112 { data: {"_": "expires",
112 { data: {"_": "expires",
113 "sort": "expires"}, title: "${_("Expires")}", width: "200px", className: "td-expire" }
113 "sort": "expires"}, title: "${_("Expires")}", width: "200px", className: "td-expire" }
114 ],
114 ],
115 language: {
115 language: {
116 paginate: DEFAULT_GRID_PAGINATION,
116 paginate: DEFAULT_GRID_PAGINATION,
117 emptyTable: _gettext("No gists available yet.")
117 emptyTable: _gettext("No gists available yet.")
118 },
118 },
119 "initComplete": function( settings, json ) {
119 "initComplete": function( settings, json ) {
120 timeagoActivate();
120 timeagoActivate();
121 tooltipActivate();
121 tooltipActivate();
122 get_datatable_count();
122 get_datatable_count();
123 }
123 }
124 });
124 });
125
125
126 // update the counter when things change
126 // update the counter when things change
127 $('#gist_list_table').on('draw.dt', function() {
127 $('#gist_list_table').on('draw.dt', function() {
128 timeagoActivate();
128 timeagoActivate();
129 tooltipActivate();
129 tooltipActivate();
130 get_datatable_count();
130 get_datatable_count();
131 });
131 });
132
132
133 // filter, filter both grids
133 // filter, filter both grids
134 $('#q_filter').on( 'keyup', function () {
134 $('#q_filter').on( 'keyup', function () {
135 var repo_api = $('#gist_list_table').dataTable().api();
135 var repo_api = $('#gist_list_table').dataTable().api();
136 repo_api
136 repo_api
137 .draw();
137 .draw();
138 });
138 });
139
139
140 // refilter table if page load via back button
140 // refilter table if page load via back button
141 $("#q_filter").trigger('keyup');
141 $("#q_filter").trigger('keyup');
142
142
143 });
143 });
144
144
145 </script>
145 </script>
146 </%def>
146 </%def>
147
147
@@ -1,219 +1,219 b''
1 <%inherit file="base.mako"/>
1 <%inherit file="base.mako"/>
2
2
3 <%def name="breadcrumbs_links()">
3 <%def name="breadcrumbs_links()">
4 %if c.repo:
4 %if c.repo:
5 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
5 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
6 %elif c.repo_group:
6 %elif c.repo_group:
7 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
7 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
8 &raquo;
8 &raquo;
9 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
9 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
11 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
12 %else:
12 %else:
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 &raquo;
14 &raquo;
15 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
15 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
16 %endif
16 %endif
17 %if c.current_IntegrationType:
17 %if c.current_IntegrationType:
18 &raquo;
18 &raquo;
19 %if c.repo:
19 %if c.repo:
20 ${h.link_to(_('Integrations'),
20 ${h.link_to(_('Integrations'),
21 request.route_path(route_name='repo_integrations_home',
21 request.route_path(route_name='repo_integrations_home',
22 repo_name=c.repo.repo_name))}
22 repo_name=c.repo.repo_name))}
23 %elif c.repo_group:
23 %elif c.repo_group:
24 ${h.link_to(_('Integrations'),
24 ${h.link_to(_('Integrations'),
25 request.route_path(route_name='repo_group_integrations_home',
25 request.route_path(route_name='repo_group_integrations_home',
26 repo_group_name=c.repo_group.group_name))}
26 repo_group_name=c.repo_group.group_name))}
27 %else:
27 %else:
28 ${h.link_to(_('Integrations'),
28 ${h.link_to(_('Integrations'),
29 request.route_path(route_name='global_integrations_home'))}
29 request.route_path(route_name='global_integrations_home'))}
30 %endif
30 %endif
31 &raquo;
31 &raquo;
32 ${c.current_IntegrationType.display_name}
32 ${c.current_IntegrationType.display_name}
33 %else:
33 %else:
34 &raquo;
34 &raquo;
35 ${_('Integrations')}
35 ${_('Integrations')}
36 %endif
36 %endif
37 </%def>
37 </%def>
38
38
39 <div class="panel panel-default">
39 <div class="panel panel-default">
40 <div class="panel-heading">
40 <div class="panel-heading">
41 <h3 class="panel-title">
41 <h3 class="panel-title">
42 %if c.repo:
42 %if c.repo:
43 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
43 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 %elif c.repo_group:
44 %elif c.repo_group:
45 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
45 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
46 %else:
46 %else:
47 ${_('Current Integrations')}
47 ${_('Current Integrations')}
48 %endif
48 %endif
49 </h3>
49 </h3>
50 </div>
50 </div>
51 <div class="panel-body">
51 <div class="panel-body">
52
52
53 <%
53 <%
54 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
54 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
55
55
56 if c.repo:
56 if c.repo:
57 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
57 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
58 elif c.repo_group:
58 elif c.repo_group:
59 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
59 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
60 else:
60 else:
61 create_url = h.route_path('global_integrations_new')
61 create_url = h.route_path('global_integrations_new')
62 %>
62 %>
63 <p class="pull-right">
63 <p class="pull-right">
64 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
64 <a href="${create_url}" class="btn btn-small btn-success">${_('Create new integration')}</a>
65 </p>
65 </p>
66
66
67 <table class="rctable integrations">
67 <table class="rctable integrations">
68 <thead>
68 <thead>
69 <tr>
69 <tr>
70 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
70 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
71 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
71 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
72 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
72 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
73 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
73 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
74 <th>${_('Actions')}</th>
74 <th>${_('Actions')}</th>
75 <th></th>
75 <th></th>
76 </tr>
76 </tr>
77 </thead>
77 </thead>
78 <tbody>
78 <tbody>
79 %if not c.integrations_list:
79 %if not c.integrations_list:
80 <tr>
80 <tr>
81 <td colspan="7">
81 <td colspan="7">
82
82
83 %if c.repo:
83 %if c.repo:
84 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
84 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
85 %elif c.repo_group:
85 %elif c.repo_group:
86 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
86 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
87 %else:
87 %else:
88 ${_('No {type} integrations exist yet.').format(type=integration_type)}
88 ${_('No {type} integrations exist yet.').format(type=integration_type)}
89 %endif
89 %endif
90
90
91 %if c.current_IntegrationType:
91 %if c.current_IntegrationType:
92 <%
92 <%
93 if c.repo:
93 if c.repo:
94 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
94 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
95 elif c.repo_group:
95 elif c.repo_group:
96 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
96 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
97 else:
97 else:
98 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
98 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
99 %>
99 %>
100 %endif
100 %endif
101
101
102 <a href="${create_url}">${_(u'Create one')}</a>
102 <a href="${create_url}">${_('Create one')}</a>
103 </td>
103 </td>
104 </tr>
104 </tr>
105 %endif
105 %endif
106 %for IntegrationType, integration in c.integrations_list:
106 %for IntegrationType, integration in c.integrations_list:
107 <tr id="integration_${integration.integration_id}">
107 <tr id="integration_${integration.integration_id}">
108 <td class="td-enabled">
108 <td class="td-enabled">
109 <div class="pull-left">
109 <div class="pull-left">
110 ${h.bool2icon(integration.enabled)}
110 ${h.bool2icon(integration.enabled)}
111 </div>
111 </div>
112 </td>
112 </td>
113 <td class="td-description">
113 <td class="td-description">
114 ${integration.name}
114 ${integration.name}
115 </td>
115 </td>
116 <td class="td-icon">
116 <td class="td-icon">
117 %if integration.integration_type in c.available_integrations:
117 %if integration.integration_type in c.available_integrations:
118 <div class="integration-icon">
118 <div class="integration-icon">
119 ${c.available_integrations[integration.integration_type].icon()|n}
119 ${c.available_integrations[integration.integration_type].icon()|n}
120 </div>
120 </div>
121 %else:
121 %else:
122 ?
122 ?
123 %endif
123 %endif
124 </td>
124 </td>
125 <td class="td-type">
125 <td class="td-type">
126 ${integration.integration_type}
126 ${integration.integration_type}
127 </td>
127 </td>
128 <td class="td-scope">
128 <td class="td-scope">
129 %if integration.repo:
129 %if integration.repo:
130 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
130 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
131 ${_('repo')}:${integration.repo.repo_name}
131 ${_('repo')}:${integration.repo.repo_name}
132 </a>
132 </a>
133 %elif integration.repo_group:
133 %elif integration.repo_group:
134 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
134 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
135 ${_('repogroup')}:${integration.repo_group.group_name}
135 ${_('repogroup')}:${integration.repo_group.group_name}
136 %if integration.child_repos_only:
136 %if integration.child_repos_only:
137 ${_('child repos only')}
137 ${_('child repos only')}
138 %else:
138 %else:
139 ${_('cascade to all')}
139 ${_('cascade to all')}
140 %endif
140 %endif
141 </a>
141 </a>
142 %else:
142 %else:
143 %if integration.child_repos_only:
143 %if integration.child_repos_only:
144 ${_('top level repos only')}
144 ${_('top level repos only')}
145 %else:
145 %else:
146 ${_('global')}
146 ${_('global')}
147 %endif
147 %endif
148 </td>
148 </td>
149 %endif
149 %endif
150 <td class="td-action">
150 <td class="td-action">
151 %if not IntegrationType:
151 %if not IntegrationType:
152 ${_('unknown integration')}
152 ${_('unknown integration')}
153 %else:
153 %else:
154 <%
154 <%
155 if c.repo:
155 if c.repo:
156 edit_url = request.route_path('repo_integrations_edit',
156 edit_url = request.route_path('repo_integrations_edit',
157 repo_name=c.repo.repo_name,
157 repo_name=c.repo.repo_name,
158 integration=integration.integration_type,
158 integration=integration.integration_type,
159 integration_id=integration.integration_id)
159 integration_id=integration.integration_id)
160 elif c.repo_group:
160 elif c.repo_group:
161 edit_url = request.route_path('repo_group_integrations_edit',
161 edit_url = request.route_path('repo_group_integrations_edit',
162 repo_group_name=c.repo_group.group_name,
162 repo_group_name=c.repo_group.group_name,
163 integration=integration.integration_type,
163 integration=integration.integration_type,
164 integration_id=integration.integration_id)
164 integration_id=integration.integration_id)
165 else:
165 else:
166 edit_url = request.route_path('global_integrations_edit',
166 edit_url = request.route_path('global_integrations_edit',
167 integration=integration.integration_type,
167 integration=integration.integration_type,
168 integration_id=integration.integration_id)
168 integration_id=integration.integration_id)
169 %>
169 %>
170 <div class="grid_edit">
170 <div class="grid_edit">
171 <a href="${edit_url}">${_('Edit')}</a>
171 <a href="${edit_url}">${_('Edit')}</a>
172 </div>
172 </div>
173 <div class="grid_delete">
173 <div class="grid_delete">
174 <a href="${edit_url}"
174 <a href="${edit_url}"
175 class="btn btn-link btn-danger delete_integration_entry"
175 class="btn btn-link btn-danger delete_integration_entry"
176 data-desc="${integration.name}"
176 data-desc="${integration.name}"
177 data-uid="${integration.integration_id}">
177 data-uid="${integration.integration_id}">
178 ${_('Delete')}
178 ${_('Delete')}
179 </a>
179 </a>
180 </div>
180 </div>
181 %endif
181 %endif
182 </td>
182 </td>
183 </tr>
183 </tr>
184 %endfor
184 %endfor
185 <tr id="last-row"></tr>
185 <tr id="last-row"></tr>
186 </tbody>
186 </tbody>
187 </table>
187 </table>
188 <div class="integrations-paginator">
188 <div class="integrations-paginator">
189 <div class="pagination-wh pagination-left">
189 <div class="pagination-wh pagination-left">
190 ${c.integrations_list.render()}
190 ${c.integrations_list.render()}
191 </div>
191 </div>
192 </div>
192 </div>
193 </div>
193 </div>
194 </div>
194 </div>
195 <script type="text/javascript">
195 <script type="text/javascript">
196 var delete_integration = function(entry) {
196 var delete_integration = function(entry) {
197 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
197 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
198 var request = $.ajax({
198 var request = $.ajax({
199 type: "POST",
199 type: "POST",
200 url: $(entry).attr('href'),
200 url: $(entry).attr('href'),
201 data: {
201 data: {
202 'delete': 'delete',
202 'delete': 'delete',
203 'csrf_token': CSRF_TOKEN
203 'csrf_token': CSRF_TOKEN
204 },
204 },
205 success: function(){
205 success: function(){
206 location.reload();
206 location.reload();
207 },
207 },
208 error: function(data, textStatus, errorThrown){
208 error: function(data, textStatus, errorThrown){
209 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
209 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
210 }
210 }
211 });
211 });
212 };
212 };
213 };
213 };
214
214
215 $('.delete_integration_entry').on('click', function(e){
215 $('.delete_integration_entry').on('click', function(e){
216 e.preventDefault();
216 e.preventDefault();
217 delete_integration(this);
217 delete_integration(this);
218 });
218 });
219 </script> No newline at end of file
219 </script>
@@ -1,67 +1,67 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Settings administration')}
4 ${_('Settings administration')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu()}
17 ${self.admin_menu()}
18 </%def>
18 </%def>
19
19
20 <%def name="side_bar_nav()">
20 <%def name="side_bar_nav()">
21
21
22 </%def>
22 </%def>
23
23
24 <%def name="main_content()">
24 <%def name="main_content()">
25 <h2>${_('Administration area')}</h2>
25 <h2>${_('Administration area')}</h2>
26
26
27 <table class="rctable">
27 <table class="rctable">
28 <tr>
28 <tr>
29 <td>${_('Repositories under administration')}</td>
29 <td>${_('Repositories under administration')}</td>
30 <td class="delegated-admin-repos">${len(c.auth_user.repositories_admin)}</td>
30 <td class="delegated-admin-repos">${len(c.auth_user.repositories_admin)}</td>
31 <td>
31 <td>
32 % if c.can_create_repo:
32 % if c.can_create_repo:
33 <a href="${h.route_path('repo_new')}" class="">${_('Add Repository')}</a>
33 <a href="${h.route_path('repo_new')}" class="">${_('Add Repository')}</a>
34 % endif
34 % endif
35 </td>
35 </td>
36 </tr>
36 </tr>
37 <tr>
37 <tr>
38 <td>${_('Repository groups under administration')}</td>
38 <td>${_('Repository groups under administration')}</td>
39 <td class="delegated-admin-repo-groups">${len(c.auth_user.repository_groups_admin)}</td>
39 <td class="delegated-admin-repo-groups">${len(c.auth_user.repository_groups_admin)}</td>
40 <td>
40 <td>
41 % if c.can_create_repo_group:
41 % if c.can_create_repo_group:
42 <a href="${h.route_path('repo_group_new')}" class="">${_(u'Add Repository Group')}</a>
42 <a href="${h.route_path('repo_group_new')}" class="">${_('Add Repository Group')}</a>
43 % endif
43 % endif
44 </td>
44 </td>
45 </tr>
45 </tr>
46 <tr>
46 <tr>
47 <td>${_('User groups under administration')}</td>
47 <td>${_('User groups under administration')}</td>
48 <td class="delegated-admin-user-groups">${len(c.auth_user.user_groups_admin)}</td>
48 <td class="delegated-admin-user-groups">${len(c.auth_user.user_groups_admin)}</td>
49 <td>
49 <td>
50 % if c.can_create_user_group:
50 % if c.can_create_user_group:
51 <a href="${h.route_path('user_groups_new')}" class="">${_(u'Add User Group')}</a>
51 <a href="${h.route_path('user_groups_new')}" class="">${_('Add User Group')}</a>
52 % endif
52 % endif
53 </td>
53 </td>
54 </tr>
54 </tr>
55 </table>
55 </table>
56 </%def>
56 </%def>
57
57
58 <%def name="main()">
58 <%def name="main()">
59 <div class="box">
59 <div class="box">
60
60
61 ##main
61 ##main
62 <div class="main-content-auto-width">
62 <div class="main-content-auto-width">
63 ${self.main_content()}
63 ${self.main_content()}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 </%def> No newline at end of file
67 </%def>
@@ -1,116 +1,116 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Repository groups administration')}
4 ${_('Repository groups administration')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='admin')}
13 ${self.menu_items(active='admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.admin_menu(active='repository_groups')}
17 ${self.admin_menu(active='repository_groups')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22
22
23 <div class="title">
23 <div class="title">
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 <span id="repo_group_count"></span>
25 <span id="repo_group_count"></span>
26
26
27 <ul class="links">
27 <ul class="links">
28 %if c.can_create_repo_group:
28 %if c.can_create_repo_group:
29 <li>
29 <li>
30 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
30 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_('Add Repository Group')}</a>
31 </li>
31 </li>
32 %endif
32 %endif
33 </ul>
33 </ul>
34 </div>
34 </div>
35 <div id="repos_list_wrap">
35 <div id="repos_list_wrap">
36 <table id="group_list_table" class="display"></table>
36 <table id="group_list_table" class="display"></table>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <script>
40 <script>
41 $(document).ready(function() {
41 $(document).ready(function() {
42 var $repoGroupsListTable = $('#group_list_table');
42 var $repoGroupsListTable = $('#group_list_table');
43
43
44 // repo group list
44 // repo group list
45 $repoGroupsListTable.DataTable({
45 $repoGroupsListTable.DataTable({
46 processing: true,
46 processing: true,
47 serverSide: true,
47 serverSide: true,
48 ajax: {
48 ajax: {
49 "url": "${h.route_path('repo_groups_data')}",
49 "url": "${h.route_path('repo_groups_data')}",
50 "dataSrc": function (json) {
50 "dataSrc": function (json) {
51 var filteredCount = json.recordsFiltered;
51 var filteredCount = json.recordsFiltered;
52 var filteredInactiveCount = json.recordsFilteredInactive;
52 var filteredInactiveCount = json.recordsFilteredInactive;
53 var totalInactive = json.recordsTotalInactive;
53 var totalInactive = json.recordsTotalInactive;
54 var total = json.recordsTotal;
54 var total = json.recordsTotal;
55
55
56 var _text = _gettext(
56 var _text = _gettext(
57 "{0} of {1} repository groups").format(
57 "{0} of {1} repository groups").format(
58 filteredCount, total);
58 filteredCount, total);
59
59
60 if (total === filteredCount) {
60 if (total === filteredCount) {
61 _text = _gettext("{0} repository groups").format(total);
61 _text = _gettext("{0} repository groups").format(total);
62 }
62 }
63 $('#repo_group_count').text(_text);
63 $('#repo_group_count').text(_text);
64 return json.data;
64 return json.data;
65 },
65 },
66 },
66 },
67
67
68 dom: 'rtp',
68 dom: 'rtp',
69 pageLength: ${c.visual.admin_grid_items},
69 pageLength: ${c.visual.admin_grid_items},
70 order: [[ 0, "asc" ]],
70 order: [[ 0, "asc" ]],
71 columns: [
71 columns: [
72 { data: {"_": "name",
72 { data: {"_": "name",
73 "sort": "name"}, title: "${_('Name')}", className: "td-componentname" },
73 "sort": "name"}, title: "${_('Name')}", className: "td-componentname" },
74 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
74 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
75 { data: {"_": "desc",
75 { data: {"_": "desc",
76 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
76 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
77 { data: {"_": "last_change",
77 { data: {"_": "last_change",
78 "sort": "last_change",
78 "sort": "last_change",
79 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
79 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
80 { data: {"_": "top_level_repos",
80 { data: {"_": "top_level_repos",
81 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
81 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
82 { data: {"_": "owner",
82 { data: {"_": "owner",
83 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
83 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
84 { data: {"_": "action",
84 { data: {"_": "action",
85 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
85 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
86 ],
86 ],
87 language: {
87 language: {
88 paginate: DEFAULT_GRID_PAGINATION,
88 paginate: DEFAULT_GRID_PAGINATION,
89 sProcessing: _gettext('loading...'),
89 sProcessing: _gettext('loading...'),
90 emptyTable: _gettext("No repository groups available yet.")
90 emptyTable: _gettext("No repository groups available yet.")
91 },
91 },
92 });
92 });
93
93
94 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
94 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
95 $repoGroupsListTable.css('opacity', 1);
95 $repoGroupsListTable.css('opacity', 1);
96 });
96 });
97
97
98 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
98 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
99 $repoGroupsListTable.css('opacity', 0.3);
99 $repoGroupsListTable.css('opacity', 0.3);
100 });
100 });
101
101
102 // filter
102 // filter
103 $('#q_filter').on('keyup',
103 $('#q_filter').on('keyup',
104 $.debounce(250, function() {
104 $.debounce(250, function() {
105 $repoGroupsListTable.DataTable().search(
105 $repoGroupsListTable.DataTable().search(
106 $('#q_filter').val()
106 $('#q_filter').val()
107 ).draw();
107 ).draw();
108 })
108 })
109 );
109 );
110
110
111 });
111 });
112
112
113 </script>
113 </script>
114
114
115 </%def>
115 </%def>
116
116
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now