##// 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
The requested commit or file is too big and content was truncated. Show full diff
1 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 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import time
20 20 import logging
21 21 import operator
22 22
23 23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
24 24
25 25 from rhodecode.lib import helpers as h, diffs, rc_cache
26 26 from rhodecode.lib.str_utils import safe_str
27 27 from rhodecode.lib.utils import repo_name_slug
28 28 from rhodecode.lib.utils2 import (
29 29 StrictAttributeDict, str2bool, safe_int, datetime_to_time)
30 30 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
31 31 from rhodecode.lib.vcs.backends.base import EmptyCommit
32 32 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
33 33 from rhodecode.model import repo
34 34 from rhodecode.model import repo_group
35 35 from rhodecode.model import user_group
36 36 from rhodecode.model import user
37 37 from rhodecode.model.db import User
38 38 from rhodecode.model.scm import ScmModel
39 39 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
40 40 from rhodecode.model.repo import ReadmeFinder
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 ADMIN_PREFIX = '/_admin'
46 STATIC_FILE_PREFIX = '/_static'
45 ADMIN_PREFIX: str = '/_admin'
46 STATIC_FILE_PREFIX: str = '/_static'
47 47
48 48 URL_NAME_REQUIREMENTS = {
49 49 # group name can have a slash in them, but they must not end with a slash
50 50 'group_name': r'.*?[^/]',
51 51 'repo_group_name': r'.*?[^/]',
52 52 # repo names can have a slash in them, but they must not end with a slash
53 53 'repo_name': r'.*?[^/]',
54 54 # file path eats up everything at the end
55 55 'f_path': r'.*',
56 56 # reference types
57 57 'source_ref_type': r'(branch|book|tag|rev|\%\(source_ref_type\)s)',
58 58 'target_ref_type': r'(branch|book|tag|rev|\%\(target_ref_type\)s)',
59 59 }
60 60
61 61
62 62 def add_route_with_slash(config,name, pattern, **kw):
63 63 config.add_route(name, pattern, **kw)
64 64 if not pattern.endswith('/'):
65 65 config.add_route(name + '_slash', pattern + '/', **kw)
66 66
67 67
68 68 def add_route_requirements(route_path, requirements=None):
69 69 """
70 70 Adds regex requirements to pyramid routes using a mapping dict
71 71 e.g::
72 72 add_route_requirements('{repo_name}/settings')
73 73 """
74 74 requirements = requirements or URL_NAME_REQUIREMENTS
75 75 for key, regex in list(requirements.items()):
76 76 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
77 77 return route_path
78 78
79 79
80 80 def get_format_ref_id(repo):
81 81 """Returns a `repo` specific reference formatter function"""
82 82 if h.is_svn(repo):
83 83 return _format_ref_id_svn
84 84 else:
85 85 return _format_ref_id
86 86
87 87
88 88 def _format_ref_id(name, raw_id):
89 89 """Default formatting of a given reference `name`"""
90 90 return name
91 91
92 92
93 93 def _format_ref_id_svn(name, raw_id):
94 94 """Special way of formatting a reference for Subversion including path"""
95 95 return f'{name}@{raw_id}'
96 96
97 97
98 98 class TemplateArgs(StrictAttributeDict):
99 99 pass
100 100
101 101
102 102 class BaseAppView(object):
103 103
104 104 def __init__(self, context, request):
105 105 self.request = request
106 106 self.context = context
107 107 self.session = request.session
108 108 if not hasattr(request, 'user'):
109 109 # NOTE(marcink): edge case, we ended up in matched route
110 110 # but probably of web-app context, e.g API CALL/VCS CALL
111 111 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
112 112 log.warning('Unable to process request `%s` in this scope', request)
113 113 raise HTTPBadRequest()
114 114
115 115 self._rhodecode_user = request.user # auth user
116 116 self._rhodecode_db_user = self._rhodecode_user.get_instance()
117 117 self._maybe_needs_password_change(
118 118 request.matched_route.name, self._rhodecode_db_user)
119 119
120 120 def _maybe_needs_password_change(self, view_name, user_obj):
121 121
122 122 dont_check_views = [
123 123 'channelstream_connect',
124 124 'ops_ping'
125 125 ]
126 126 if view_name in dont_check_views:
127 127 return
128 128
129 129 log.debug('Checking if user %s needs password change on view %s',
130 130 user_obj, view_name)
131 131
132 132 skip_user_views = [
133 133 'logout', 'login',
134 134 'my_account_password', 'my_account_password_update'
135 135 ]
136 136
137 137 if not user_obj:
138 138 return
139 139
140 140 if user_obj.username == User.DEFAULT_USER:
141 141 return
142 142
143 143 now = time.time()
144 144 should_change = user_obj.user_data.get('force_password_change')
145 145 change_after = safe_int(should_change) or 0
146 146 if should_change and now > change_after:
147 147 log.debug('User %s requires password change', user_obj)
148 148 h.flash('You are required to change your password', 'warning',
149 149 ignore_duplicate=True)
150 150
151 151 if view_name not in skip_user_views:
152 152 raise HTTPFound(
153 153 self.request.route_path('my_account_password'))
154 154
155 155 def _log_creation_exception(self, e, repo_name):
156 156 _ = self.request.translate
157 157 reason = None
158 158 if len(e.args) == 2:
159 159 reason = e.args[1]
160 160
161 161 if reason == 'INVALID_CERTIFICATE':
162 162 log.exception(
163 163 'Exception creating a repository: invalid certificate')
164 164 msg = (_('Error creating repository %s: invalid certificate')
165 165 % repo_name)
166 166 else:
167 167 log.exception("Exception creating a repository")
168 168 msg = (_('Error creating repository %s')
169 169 % repo_name)
170 170 return msg
171 171
172 172 def _get_local_tmpl_context(self, include_app_defaults=True):
173 173 c = TemplateArgs()
174 174 c.auth_user = self.request.user
175 175 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
176 176 c.rhodecode_user = self.request.user
177 177
178 178 if include_app_defaults:
179 179 from rhodecode.lib.base import attach_context_attributes
180 180 attach_context_attributes(c, self.request, self.request.user.user_id)
181 181
182 182 c.is_super_admin = c.auth_user.is_admin
183 183
184 184 c.can_create_repo = c.is_super_admin
185 185 c.can_create_repo_group = c.is_super_admin
186 186 c.can_create_user_group = c.is_super_admin
187 187
188 188 c.is_delegated_admin = False
189 189
190 190 if not c.auth_user.is_default and not c.is_super_admin:
191 191 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
192 192 user=self.request.user)
193 193 repositories = c.auth_user.repositories_admin or c.can_create_repo
194 194
195 195 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
196 196 user=self.request.user)
197 197 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
198 198
199 199 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
200 200 user=self.request.user)
201 201 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
202 202 # delegated admin can create, or manage some objects
203 203 c.is_delegated_admin = repositories or repository_groups or user_groups
204 204 return c
205 205
206 206 def _get_template_context(self, tmpl_args, **kwargs):
207 207
208 208 local_tmpl_args = {
209 209 'defaults': {},
210 210 'errors': {},
211 211 'c': tmpl_args
212 212 }
213 213 local_tmpl_args.update(kwargs)
214 214 return local_tmpl_args
215 215
216 216 def load_default_context(self):
217 217 """
218 218 example:
219 219
220 220 def load_default_context(self):
221 221 c = self._get_local_tmpl_context()
222 222 c.custom_var = 'foobar'
223 223
224 224 return c
225 225 """
226 226 raise NotImplementedError('Needs implementation in view class')
227 227
228 228
229 229 class RepoAppView(BaseAppView):
230 230
231 231 def __init__(self, context, request):
232 232 super().__init__(context, request)
233 233 self.db_repo = request.db_repo
234 234 self.db_repo_name = self.db_repo.repo_name
235 235 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
236 236 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
237 237 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
238 238
239 239 def _handle_missing_requirements(self, error):
240 240 log.error(
241 241 'Requirements are missing for repository %s: %s',
242 242 self.db_repo_name, safe_str(error))
243 243
244 244 def _prepare_and_set_clone_url(self, c):
245 245 username = ''
246 246 if self._rhodecode_user.username != User.DEFAULT_USER:
247 247 username = self._rhodecode_user.username
248 248
249 249 _def_clone_uri = c.clone_uri_tmpl
250 250 _def_clone_uri_id = c.clone_uri_id_tmpl
251 251 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
252 252
253 253 c.clone_repo_url = self.db_repo.clone_url(
254 254 user=username, uri_tmpl=_def_clone_uri)
255 255 c.clone_repo_url_id = self.db_repo.clone_url(
256 256 user=username, uri_tmpl=_def_clone_uri_id)
257 257 c.clone_repo_url_ssh = self.db_repo.clone_url(
258 258 uri_tmpl=_def_clone_uri_ssh, ssh=True)
259 259
260 260 def _get_local_tmpl_context(self, include_app_defaults=True):
261 261 _ = self.request.translate
262 262 c = super()._get_local_tmpl_context(
263 263 include_app_defaults=include_app_defaults)
264 264
265 265 # register common vars for this type of view
266 266 c.rhodecode_db_repo = self.db_repo
267 267 c.repo_name = self.db_repo_name
268 268 c.repository_pull_requests = self.db_repo_pull_requests
269 269 c.repository_artifacts = self.db_repo_artifacts
270 270 c.repository_is_user_following = ScmModel().is_following_repo(
271 271 self.db_repo_name, self._rhodecode_user.user_id)
272 272 self.path_filter = PathFilter(None)
273 273
274 274 c.repository_requirements_missing = {}
275 275 try:
276 276 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
277 277 # NOTE(marcink):
278 278 # comparison to None since if it's an object __bool__ is expensive to
279 279 # calculate
280 280 if self.rhodecode_vcs_repo is not None:
281 281 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
282 282 c.auth_user.username)
283 283 self.path_filter = PathFilter(path_perms)
284 284 except RepositoryRequirementError as e:
285 285 c.repository_requirements_missing = {'error': str(e)}
286 286 self._handle_missing_requirements(e)
287 287 self.rhodecode_vcs_repo = None
288 288
289 289 c.path_filter = self.path_filter # used by atom_feed_entry.mako
290 290
291 291 if self.rhodecode_vcs_repo is None:
292 292 # unable to fetch this repo as vcs instance, report back to user
293 293 log.debug('Repository was not found on filesystem, check if it exists or is not damaged')
294 294 h.flash(_(
295 295 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
296 296 "Please check if it exist, or is not damaged.") %
297 297 {'repo_name': c.repo_name},
298 298 category='error', ignore_duplicate=True)
299 299 if c.repository_requirements_missing:
300 300 route = self.request.matched_route.name
301 301 if route.startswith(('edit_repo', 'repo_summary')):
302 302 # allow summary and edit repo on missing requirements
303 303 return c
304 304
305 305 raise HTTPFound(
306 306 h.route_path('repo_summary', repo_name=self.db_repo_name))
307 307
308 308 else: # redirect if we don't show missing requirements
309 309 raise HTTPFound(h.route_path('home'))
310 310
311 311 c.has_origin_repo_read_perm = False
312 312 if self.db_repo.fork:
313 313 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
314 314 'repository.write', 'repository.read', 'repository.admin')(
315 315 self.db_repo.fork.repo_name, 'summary fork link')
316 316
317 317 return c
318 318
319 319 def _get_f_path_unchecked(self, matchdict, default=None):
320 320 """
321 321 Should only be used by redirects, everything else should call _get_f_path
322 322 """
323 323 f_path = matchdict.get('f_path')
324 324 if f_path:
325 325 # fix for multiple initial slashes that causes errors for GIT
326 326 return f_path.lstrip('/')
327 327
328 328 return default
329 329
330 330 def _get_f_path(self, matchdict, default=None):
331 331 f_path_match = self._get_f_path_unchecked(matchdict, default)
332 332 return self.path_filter.assert_path_permissions(f_path_match)
333 333
334 334 def _get_general_setting(self, target_repo, settings_key, default=False):
335 335 settings_model = VcsSettingsModel(repo=target_repo)
336 336 settings = settings_model.get_general_settings()
337 337 return settings.get(settings_key, default)
338 338
339 339 def _get_repo_setting(self, target_repo, settings_key, default=False):
340 340 settings_model = VcsSettingsModel(repo=target_repo)
341 341 settings = settings_model.get_repo_settings_inherited()
342 342 return settings.get(settings_key, default)
343 343
344 344 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
345 345 log.debug('Looking for README file at path %s', path)
346 346 if commit_id:
347 347 landing_commit_id = commit_id
348 348 else:
349 349 landing_commit = db_repo.get_landing_commit()
350 350 if isinstance(landing_commit, EmptyCommit):
351 351 return None, None
352 352 landing_commit_id = landing_commit.raw_id
353 353
354 354 cache_namespace_uid = f'repo.{db_repo.repo_id}'
355 355 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid, use_async_runner=True)
356 356 start = time.time()
357 357
358 358 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
359 359 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
360 360 readme_data = None
361 361 readme_filename = None
362 362
363 363 commit = db_repo.get_commit(_commit_id)
364 364 log.debug("Searching for a README file at commit %s.", _commit_id)
365 365 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
366 366
367 367 if readme_node:
368 368 log.debug('Found README node: %s', readme_node)
369 369 relative_urls = {
370 370 'raw': h.route_path(
371 371 'repo_file_raw', repo_name=_repo_name,
372 372 commit_id=commit.raw_id, f_path=readme_node.path),
373 373 'standard': h.route_path(
374 374 'repo_files', repo_name=_repo_name,
375 375 commit_id=commit.raw_id, f_path=readme_node.path),
376 376 }
377 377
378 378 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
379 379 readme_filename = readme_node.str_path
380 380
381 381 return readme_data, readme_filename
382 382
383 383 readme_data, readme_filename = generate_repo_readme(
384 384 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
385 385
386 386 compute_time = time.time() - start
387 387 log.debug('Repo README for path %s generated and computed in %.4fs',
388 388 path, compute_time)
389 389 return readme_data, readme_filename
390 390
391 391 def _render_readme_or_none(self, commit, readme_node, relative_urls):
392 392 log.debug('Found README file `%s` rendering...', readme_node.path)
393 393 renderer = MarkupRenderer()
394 394 try:
395 395 html_source = renderer.render(
396 396 readme_node.str_content, filename=readme_node.path)
397 397 if relative_urls:
398 398 return relative_links(html_source, relative_urls)
399 399 return html_source
400 400 except Exception:
401 401 log.exception("Exception while trying to render the README")
402 402
403 403 def get_recache_flag(self):
404 404 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
405 405 flag_val = self.request.GET.get(flag_name)
406 406 if str2bool(flag_val):
407 407 return True
408 408 return False
409 409
410 410 def get_commit_preload_attrs(cls):
411 411 pre_load = ['author', 'branch', 'date', 'message', 'parents',
412 412 'obsolete', 'phase', 'hidden']
413 413 return pre_load
414 414
415 415
416 416 class PathFilter(object):
417 417
418 418 # Expects and instance of BasePathPermissionChecker or None
419 419 def __init__(self, permission_checker):
420 420 self.permission_checker = permission_checker
421 421
422 422 def assert_path_permissions(self, path):
423 423 if self.path_access_allowed(path):
424 424 return path
425 425 raise HTTPForbidden()
426 426
427 427 def path_access_allowed(self, path):
428 428 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
429 429 if self.permission_checker:
430 430 has_access = path and self.permission_checker.has_access(path)
431 431 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
432 432 return has_access
433 433
434 434 log.debug('ACL permissions checker not enabled, skipping...')
435 435 return True
436 436
437 437 def filter_patchset(self, patchset):
438 438 if not self.permission_checker or not patchset:
439 439 return patchset, False
440 440 had_filtered = False
441 441 filtered_patchset = []
442 442 for patch in patchset:
443 443 filename = patch.get('filename', None)
444 444 if not filename or self.permission_checker.has_access(filename):
445 445 filtered_patchset.append(patch)
446 446 else:
447 447 had_filtered = True
448 448 if had_filtered:
449 449 if isinstance(patchset, diffs.LimitedDiffContainer):
450 450 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
451 451 return filtered_patchset, True
452 452 else:
453 453 return patchset, False
454 454
455 455 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
456 456
457 457 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
458 458 result = diffset.render_patchset(
459 459 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
460 460 result.has_hidden_changes = has_hidden_changes
461 461 return result
462 462
463 463 def get_raw_patch(self, diff_processor):
464 464 if self.permission_checker is None:
465 465 return diff_processor.as_raw()
466 466 elif self.permission_checker.has_full_access:
467 467 return diff_processor.as_raw()
468 468 else:
469 469 return '# Repository has user-specific filters, raw patch generation is disabled.'
470 470
471 471 @property
472 472 def is_enabled(self):
473 473 return self.permission_checker is not None
474 474
475 475
476 476 class RepoGroupAppView(BaseAppView):
477 477 def __init__(self, context, request):
478 478 super().__init__(context, request)
479 479 self.db_repo_group = request.db_repo_group
480 480 self.db_repo_group_name = self.db_repo_group.group_name
481 481
482 482 def _get_local_tmpl_context(self, include_app_defaults=True):
483 483 _ = self.request.translate
484 484 c = super()._get_local_tmpl_context(
485 485 include_app_defaults=include_app_defaults)
486 486 c.repo_group = self.db_repo_group
487 487 return c
488 488
489 489 def _revoke_perms_on_yourself(self, form_result):
490 490 _updates = [u for u in form_result['perm_updates'] if self._rhodecode_user.user_id == int(u[0])]
491 491 _additions = [u for u in form_result['perm_additions'] if self._rhodecode_user.user_id == int(u[0])]
492 492 _deletions = [u for u in form_result['perm_deletions'] if self._rhodecode_user.user_id == int(u[0])]
493 493 admin_perm = 'group.admin'
494 494 if _updates and _updates[0][1] != admin_perm or \
495 495 _additions and _additions[0][1] != admin_perm or \
496 496 _deletions and _deletions[0][1] != admin_perm:
497 497 return True
498 498 return False
499 499
500 500
501 501 class UserGroupAppView(BaseAppView):
502 502 def __init__(self, context, request):
503 503 super().__init__(context, request)
504 504 self.db_user_group = request.db_user_group
505 505 self.db_user_group_name = self.db_user_group.users_group_name
506 506
507 507
508 508 class UserAppView(BaseAppView):
509 509 def __init__(self, context, request):
510 510 super().__init__(context, request)
511 511 self.db_user = request.db_user
512 512 self.db_user_id = self.db_user.user_id
513 513
514 514 _ = self.request.translate
515 515 if not request.db_user_supports_default:
516 516 if self.db_user.username == User.DEFAULT_USER:
517 517 h.flash(_("Editing user `{}` is disabled.".format(
518 518 User.DEFAULT_USER)), category='warning')
519 519 raise HTTPFound(h.route_path('users'))
520 520
521 521
522 522 class DataGridAppView(object):
523 523 """
524 524 Common class to have re-usable grid rendering components
525 525 """
526 526
527 527 def _extract_ordering(self, request, column_map=None):
528 528 column_map = column_map or {}
529 529 column_index = safe_int(request.GET.get('order[0][column]'))
530 530 order_dir = request.GET.get(
531 531 'order[0][dir]', 'desc')
532 532 order_by = request.GET.get(
533 533 'columns[%s][data][sort]' % column_index, 'name_raw')
534 534
535 535 # translate datatable to DB columns
536 536 order_by = column_map.get(order_by) or order_by
537 537
538 538 search_q = request.GET.get('search[value]')
539 539 return search_q, order_by, order_dir
540 540
541 541 def _extract_chunk(self, request):
542 542 start = safe_int(request.GET.get('start'), 0)
543 543 length = safe_int(request.GET.get('length'), 25)
544 544 draw = safe_int(request.GET.get('draw'))
545 545 return draw, start, length
546 546
547 547 def _get_order_col(self, order_by, model):
548 548 if isinstance(order_by, str):
549 549 try:
550 550 return operator.attrgetter(order_by)(model)
551 551 except AttributeError:
552 552 return None
553 553 else:
554 554 return order_by
555 555
556 556
557 557 class BaseReferencesView(RepoAppView):
558 558 """
559 559 Base for reference view for branches, tags and bookmarks.
560 560 """
561 561 def load_default_context(self):
562 562 c = self._get_local_tmpl_context()
563 563 return c
564 564
565 565 def load_refs_context(self, ref_items, partials_template):
566 566 _render = self.request.get_partial_renderer(partials_template)
567 567 pre_load = ["author", "date", "message", "parents"]
568 568
569 569 is_svn = h.is_svn(self.rhodecode_vcs_repo)
570 570 is_hg = h.is_hg(self.rhodecode_vcs_repo)
571 571
572 572 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
573 573
574 574 closed_refs = {}
575 575 if is_hg:
576 576 closed_refs = self.rhodecode_vcs_repo.branches_closed
577 577
578 578 data = []
579 579 for ref_name, commit_id in ref_items:
580 580 commit = self.rhodecode_vcs_repo.get_commit(
581 581 commit_id=commit_id, pre_load=pre_load)
582 582 closed = ref_name in closed_refs
583 583
584 584 # TODO: johbo: Unify generation of reference links
585 585 use_commit_id = '/' in ref_name or is_svn
586 586
587 587 if use_commit_id:
588 588 files_url = h.route_path(
589 589 'repo_files',
590 590 repo_name=self.db_repo_name,
591 591 f_path=ref_name if is_svn else '',
592 592 commit_id=commit_id,
593 593 _query=dict(at=ref_name)
594 594 )
595 595
596 596 else:
597 597 files_url = h.route_path(
598 598 'repo_files',
599 599 repo_name=self.db_repo_name,
600 600 f_path=ref_name if is_svn else '',
601 601 commit_id=ref_name,
602 602 _query=dict(at=ref_name)
603 603 )
604 604
605 605 data.append({
606 606 "name": _render('name', ref_name, files_url, closed),
607 607 "name_raw": ref_name,
608 608 "date": _render('date', commit.date),
609 609 "date_raw": datetime_to_time(commit.date),
610 610 "author": _render('author', commit.author),
611 611 "commit": _render(
612 612 'commit', commit.message, commit.raw_id, commit.idx),
613 613 "commit_raw": commit.idx,
614 614 "compare": _render(
615 615 'compare', format_ref_id(ref_name, commit.raw_id)),
616 616 })
617 617
618 618 return data
619 619
620 620
621 621 class RepoRoutePredicate(object):
622 622 def __init__(self, val, config):
623 623 self.val = val
624 624
625 625 def text(self):
626 626 return f'repo_route = {self.val}'
627 627
628 628 phash = text
629 629
630 630 def __call__(self, info, request):
631 631 if hasattr(request, 'vcs_call'):
632 632 # skip vcs calls
633 633 return
634 634
635 635 repo_name = info['match']['repo_name']
636 636
637 637 repo_name_parts = repo_name.split('/')
638 638 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
639 639
640 640 if repo_name_parts != repo_slugs:
641 641 # short-skip if the repo-name doesn't follow slug rule
642 642 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
643 643 return False
644 644
645 645 repo_model = repo.RepoModel()
646 646
647 647 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
648 648
649 649 def redirect_if_creating(route_info, db_repo):
650 650 skip_views = ['edit_repo_advanced_delete']
651 651 route = route_info['route']
652 652 # we should skip delete view so we can actually "remove" repositories
653 653 # if they get stuck in creating state.
654 654 if route.name in skip_views:
655 655 return
656 656
657 657 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
658 658 repo_creating_url = request.route_path(
659 659 'repo_creating', repo_name=db_repo.repo_name)
660 660 raise HTTPFound(repo_creating_url)
661 661
662 662 if by_name_match:
663 663 # register this as request object we can re-use later
664 664 request.db_repo = by_name_match
665 665 request.db_repo_name = request.db_repo.repo_name
666 666
667 667 redirect_if_creating(info, by_name_match)
668 668 return True
669 669
670 670 by_id_match = repo_model.get_repo_by_id(repo_name)
671 671 if by_id_match:
672 672 request.db_repo = by_id_match
673 673 request.db_repo_name = request.db_repo.repo_name
674 674 redirect_if_creating(info, by_id_match)
675 675 return True
676 676
677 677 return False
678 678
679 679
680 680 class RepoForbidArchivedRoutePredicate(object):
681 681 def __init__(self, val, config):
682 682 self.val = val
683 683
684 684 def text(self):
685 685 return f'repo_forbid_archived = {self.val}'
686 686
687 687 phash = text
688 688
689 689 def __call__(self, info, request):
690 690 _ = request.translate
691 691 rhodecode_db_repo = request.db_repo
692 692
693 693 log.debug(
694 694 '%s checking if archived flag for repo for %s',
695 695 self.__class__.__name__, rhodecode_db_repo.repo_name)
696 696
697 697 if rhodecode_db_repo.archived:
698 698 log.warning('Current view is not supported for archived repo:%s',
699 699 rhodecode_db_repo.repo_name)
700 700
701 701 h.flash(
702 702 h.literal(_('Action not supported for archived repository.')),
703 703 category='warning')
704 704 summary_url = request.route_path(
705 705 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
706 706 raise HTTPFound(summary_url)
707 707 return True
708 708
709 709
710 710 class RepoTypeRoutePredicate(object):
711 711 def __init__(self, val, config):
712 712 self.val = val or ['hg', 'git', 'svn']
713 713
714 714 def text(self):
715 715 return f'repo_accepted_type = {self.val}'
716 716
717 717 phash = text
718 718
719 719 def __call__(self, info, request):
720 720 if hasattr(request, 'vcs_call'):
721 721 # skip vcs calls
722 722 return
723 723
724 724 rhodecode_db_repo = request.db_repo
725 725
726 726 log.debug(
727 727 '%s checking repo type for %s in %s',
728 728 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
729 729
730 730 if rhodecode_db_repo.repo_type in self.val:
731 731 return True
732 732 else:
733 733 log.warning('Current view is not supported for repo type:%s',
734 734 rhodecode_db_repo.repo_type)
735 735 return False
736 736
737 737
738 738 class RepoGroupRoutePredicate(object):
739 739 def __init__(self, val, config):
740 740 self.val = val
741 741
742 742 def text(self):
743 743 return f'repo_group_route = {self.val}'
744 744
745 745 phash = text
746 746
747 747 def __call__(self, info, request):
748 748 if hasattr(request, 'vcs_call'):
749 749 # skip vcs calls
750 750 return
751 751
752 752 repo_group_name = info['match']['repo_group_name']
753 753
754 754 repo_group_name_parts = repo_group_name.split('/')
755 755 repo_group_slugs = [x for x in [repo_name_slug(x) for x in repo_group_name_parts]]
756 756 if repo_group_name_parts != repo_group_slugs:
757 757 # short-skip if the repo-name doesn't follow slug rule
758 758 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
759 759 return False
760 760
761 761 repo_group_model = repo_group.RepoGroupModel()
762 762 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
763 763
764 764 if by_name_match:
765 765 # register this as request object we can re-use later
766 766 request.db_repo_group = by_name_match
767 767 request.db_repo_group_name = request.db_repo_group.group_name
768 768 return True
769 769
770 770 return False
771 771
772 772
773 773 class UserGroupRoutePredicate(object):
774 774 def __init__(self, val, config):
775 775 self.val = val
776 776
777 777 def text(self):
778 778 return f'user_group_route = {self.val}'
779 779
780 780 phash = text
781 781
782 782 def __call__(self, info, request):
783 783 if hasattr(request, 'vcs_call'):
784 784 # skip vcs calls
785 785 return
786 786
787 787 user_group_id = info['match']['user_group_id']
788 788 user_group_model = user_group.UserGroup()
789 789 by_id_match = user_group_model.get(user_group_id, cache=False)
790 790
791 791 if by_id_match:
792 792 # register this as request object we can re-use later
793 793 request.db_user_group = by_id_match
794 794 return True
795 795
796 796 return False
797 797
798 798
799 799 class UserRoutePredicateBase(object):
800 800 supports_default = None
801 801
802 802 def __init__(self, val, config):
803 803 self.val = val
804 804
805 805 def text(self):
806 806 raise NotImplementedError()
807 807
808 808 def __call__(self, info, request):
809 809 if hasattr(request, 'vcs_call'):
810 810 # skip vcs calls
811 811 return
812 812
813 813 user_id = info['match']['user_id']
814 814 user_model = user.User()
815 815 by_id_match = user_model.get(user_id, cache=False)
816 816
817 817 if by_id_match:
818 818 # register this as request object we can re-use later
819 819 request.db_user = by_id_match
820 820 request.db_user_supports_default = self.supports_default
821 821 return True
822 822
823 823 return False
824 824
825 825
826 826 class UserRoutePredicate(UserRoutePredicateBase):
827 827 supports_default = False
828 828
829 829 def text(self):
830 830 return f'user_route = {self.val}'
831 831
832 832 phash = text
833 833
834 834
835 835 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
836 836 supports_default = True
837 837
838 838 def text(self):
839 839 return f'user_with_default_route = {self.val}'
840 840
841 841 phash = text
842 842
843 843
844 844 def includeme(config):
845 845 config.add_route_predicate(
846 846 'repo_route', RepoRoutePredicate)
847 847 config.add_route_predicate(
848 848 'repo_accepted_types', RepoTypeRoutePredicate)
849 849 config.add_route_predicate(
850 850 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
851 851 config.add_route_predicate(
852 852 'repo_group_route', RepoGroupRoutePredicate)
853 853 config.add_route_predicate(
854 854 'user_group_route', UserGroupRoutePredicate)
855 855 config.add_route_predicate(
856 856 'user_route_with_default', UserRouteWithDefaultPredicate)
857 857 config.add_route_predicate(
858 858 'user_route', UserRoutePredicate)
@@ -1,154 +1,152 b''
1 1
2 2 import dataclasses
3 3 # Copyright (C) 2016-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import collections
24 24
25 25 from zope.interface import implementer
26 26
27 27 from rhodecode.apps._base.interfaces import IAdminNavigationRegistry
28 28 from rhodecode.lib.utils2 import str2bool
29 29 from rhodecode.translation import _
30 30
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 @dataclasses.dataclass
36 36 class NavListEntry:
37 37 key: str
38 38 name: str
39 39 url: str
40 40 active_list: list
41 41
42 42
43 43 class NavEntry(object):
44 44 """
45 45 Represents an entry in the admin navigation.
46 46
47 47 :param key: Unique identifier used to store reference in an OrderedDict.
48 48 :param name: Display name, usually a translation string.
49 49 :param view_name: Name of the view, used generate the URL.
50 50 :param active_list: list of urls that we select active for this element
51 51 """
52 52
53 53 def __init__(self, key, name, view_name, active_list=None):
54 54 self.key = key
55 55 self.name = name
56 56 self.view_name = view_name
57 57 self._active_list = active_list or []
58 58
59 59 def generate_url(self, request):
60 60 return request.route_path(self.view_name)
61 61
62 62 def get_localized_name(self, request):
63 63 return request.translate(self.name)
64 64
65 65 @property
66 66 def active_list(self):
67 67 active_list = [self.key]
68 68 if self._active_list:
69 69 active_list = self._active_list
70 70 return active_list
71 71
72 72
73 73 @implementer(IAdminNavigationRegistry)
74 74 class NavigationRegistry(object):
75 75
76 76 _base_entries = [
77 77 NavEntry('global', _('Global'),
78 78 'admin_settings_global'),
79 79 NavEntry('vcs', _('VCS'),
80 80 'admin_settings_vcs'),
81 81 NavEntry('visual', _('Visual'),
82 82 'admin_settings_visual'),
83 83 NavEntry('mapping', _('Remap and Rescan'),
84 84 'admin_settings_mapping'),
85 85 NavEntry('issuetracker', _('Issue Tracker'),
86 86 'admin_settings_issuetracker'),
87 87 NavEntry('email', _('Email'),
88 88 'admin_settings_email'),
89 89 NavEntry('hooks', _('Hooks'),
90 90 'admin_settings_hooks'),
91 91 NavEntry('search', _('Full Text Search'),
92 92 'admin_settings_search'),
93 93 NavEntry('system', _('System Info'),
94 94 'admin_settings_system'),
95 95 NavEntry('exceptions', _('Exceptions Tracker'),
96 96 'admin_settings_exception_tracker',
97 97 active_list=['exceptions', 'exceptions_browse']),
98 98 NavEntry('process_management', _('Processes'),
99 99 'admin_settings_process_management'),
100 100 NavEntry('sessions', _('User Sessions'),
101 101 'admin_settings_sessions'),
102 102 NavEntry('open_source', _('Open Source Licenses'),
103 103 'admin_settings_open_source'),
104 NavEntry('automation', _('Automation'),
105 'admin_settings_automation')
106 104 ]
107 105
108 106 _labs_entry = NavEntry('labs', _('Labs'),
109 107 'admin_settings_labs')
110 108
111 109 def __init__(self, labs_active=False):
112 110 self._registered_entries = collections.OrderedDict()
113 111 for item in self.__class__._base_entries:
114 112 self.add_entry(item)
115 113
116 114 if labs_active:
117 115 self.add_entry(self._labs_entry)
118 116
119 117 def add_entry(self, entry):
120 118 self._registered_entries[entry.key] = entry
121 119
122 120 def get_navlist(self, request):
123 121 nav_list = [
124 122 NavListEntry(i.key, i.get_localized_name(request),
125 123 i.generate_url(request), i.active_list)
126 124 for i in self._registered_entries.values()
127 125 ]
128 126 return nav_list
129 127
130 128
131 129 def navigation_registry(request, registry=None):
132 130 """
133 131 Helper that returns the admin navigation registry.
134 132 """
135 133 pyramid_registry = registry or request.registry
136 134 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
137 135 return nav_registry
138 136
139 137
140 138 def navigation_list(request):
141 139 """
142 140 Helper that returns the admin navigation as list of NavListEntry objects.
143 141 """
144 142 return navigation_registry(request).get_navlist(request)
145 143
146 144
147 145 def includeme(config):
148 146 # Create admin navigation registry and add it to the pyramid registry.
149 147 settings = config.get_settings()
150 148 labs_active = str2bool(settings.get('labs_settings_active', False))
151 149 navigation_registry_instance = NavigationRegistry(labs_active=labs_active)
152 150 config.registry.registerUtility(navigation_registry_instance)
153 151 log.debug('Created new navigation instance, %s', navigation_registry_instance)
154 152
@@ -1,1082 +1,1093 b''
1 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 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 25 def admin_routes(config):
24 26 """
25 27 Admin prefixed routes
26 28 """
27 29 from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
28 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 33 from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
30 34 from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
31 from rhodecode.apps.admin.views.main_views import AdminMainView
32 35 from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
33 36 from rhodecode.apps.admin.views.permissions import AdminPermissionsView
34 37 from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
35 38 from rhodecode.apps.admin.views.repo_groups import AdminRepoGroupsView
36 39 from rhodecode.apps.admin.views.repositories import AdminReposView
37 40 from rhodecode.apps.admin.views.sessions import AdminSessionSettingsView
38 41 from rhodecode.apps.admin.views.settings import AdminSettingsView
39 42 from rhodecode.apps.admin.views.svn_config import AdminSvnConfigView
40 43 from rhodecode.apps.admin.views.system_info import AdminSystemInfoSettingsView
41 44 from rhodecode.apps.admin.views.user_groups import AdminUserGroupsView
42 45 from rhodecode.apps.admin.views.users import AdminUsersView, UsersView
43 46
44 47 config.add_route(
45 48 name='admin_audit_logs',
46 49 pattern='/audit_logs')
47 50 config.add_view(
48 51 AdminAuditLogsView,
49 52 attr='admin_audit_logs',
50 53 route_name='admin_audit_logs', request_method='GET',
51 54 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
52 55
53 56 config.add_route(
54 57 name='admin_audit_log_entry',
55 58 pattern='/audit_logs/{audit_log_id}')
56 59 config.add_view(
57 60 AdminAuditLogsView,
58 61 attr='admin_audit_log_entry',
59 62 route_name='admin_audit_log_entry', request_method='GET',
60 63 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
61 64
62 65 # Artifacts EE feature
63 66 config.add_route(
64 67 'admin_artifacts',
65 68 pattern=ADMIN_PREFIX + '/artifacts')
66 69 config.add_route(
67 70 'admin_artifacts_show_all',
68 71 pattern=ADMIN_PREFIX + '/artifacts')
69 72 config.add_view(
70 73 AdminArtifactsView,
71 74 attr='artifacts',
72 75 route_name='admin_artifacts', request_method='GET',
73 76 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
74 77 config.add_view(
75 78 AdminArtifactsView,
76 79 attr='artifacts',
77 80 route_name='admin_artifacts_show_all', request_method='GET',
78 81 renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
82
79 83 # EE views
80 84 config.add_route(
81 85 name='admin_artifacts_show_info',
82 86 pattern=ADMIN_PREFIX + '/artifacts/{uid}')
83 87 config.add_route(
84 88 name='admin_artifacts_delete',
85 89 pattern=ADMIN_PREFIX + '/artifacts/{uid}/delete')
86 90 config.add_route(
87 91 name='admin_artifacts_update',
88 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 114 config.add_route(
91 115 name='admin_settings_open_source',
92 116 pattern='/settings/open_source')
93 117 config.add_view(
94 118 OpenSourceLicensesAdminSettingsView,
95 119 attr='open_source_licenses',
96 120 route_name='admin_settings_open_source', request_method='GET',
97 121 renderer='rhodecode:templates/admin/settings/settings.mako')
98 122
99 123 config.add_route(
100 124 name='admin_settings_vcs_svn_generate_cfg',
101 125 pattern='/settings/vcs/svn_generate_cfg')
102 126 config.add_view(
103 127 AdminSvnConfigView,
104 128 attr='vcs_svn_generate_config',
105 129 route_name='admin_settings_vcs_svn_generate_cfg',
106 130 request_method='POST', renderer='json')
107 131
108 132 config.add_route(
109 133 name='admin_settings_system',
110 134 pattern='/settings/system')
111 135 config.add_view(
112 136 AdminSystemInfoSettingsView,
113 137 attr='settings_system_info',
114 138 route_name='admin_settings_system', request_method='GET',
115 139 renderer='rhodecode:templates/admin/settings/settings.mako')
116 140
117 141 config.add_route(
118 142 name='admin_settings_system_update',
119 143 pattern='/settings/system/updates')
120 144 config.add_view(
121 145 AdminSystemInfoSettingsView,
122 146 attr='settings_system_info_check_update',
123 147 route_name='admin_settings_system_update', request_method='GET',
124 148 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
125 149
126 150 config.add_route(
127 151 name='admin_settings_exception_tracker',
128 152 pattern='/settings/exceptions')
129 153 config.add_view(
130 154 ExceptionsTrackerView,
131 155 attr='browse_exceptions',
132 156 route_name='admin_settings_exception_tracker', request_method='GET',
133 157 renderer='rhodecode:templates/admin/settings/settings.mako')
134 158
135 159 config.add_route(
136 160 name='admin_settings_exception_tracker_delete_all',
137 161 pattern='/settings/exceptions_delete_all')
138 162 config.add_view(
139 163 ExceptionsTrackerView,
140 164 attr='exception_delete_all',
141 165 route_name='admin_settings_exception_tracker_delete_all', request_method='POST',
142 166 renderer='rhodecode:templates/admin/settings/settings.mako')
143 167
144 168 config.add_route(
145 169 name='admin_settings_exception_tracker_show',
146 170 pattern='/settings/exceptions/{exception_id}')
147 171 config.add_view(
148 172 ExceptionsTrackerView,
149 173 attr='exception_show',
150 174 route_name='admin_settings_exception_tracker_show', request_method='GET',
151 175 renderer='rhodecode:templates/admin/settings/settings.mako')
152 176
153 177 config.add_route(
154 178 name='admin_settings_exception_tracker_delete',
155 179 pattern='/settings/exceptions/{exception_id}/delete')
156 180 config.add_view(
157 181 ExceptionsTrackerView,
158 182 attr='exception_delete',
159 183 route_name='admin_settings_exception_tracker_delete', request_method='POST',
160 184 renderer='rhodecode:templates/admin/settings/settings.mako')
161 185
162 186 config.add_route(
163 187 name='admin_settings_sessions',
164 188 pattern='/settings/sessions')
165 189 config.add_view(
166 190 AdminSessionSettingsView,
167 191 attr='settings_sessions',
168 192 route_name='admin_settings_sessions', request_method='GET',
169 193 renderer='rhodecode:templates/admin/settings/settings.mako')
170 194
171 195 config.add_route(
172 196 name='admin_settings_sessions_cleanup',
173 197 pattern='/settings/sessions/cleanup')
174 198 config.add_view(
175 199 AdminSessionSettingsView,
176 200 attr='settings_sessions_cleanup',
177 201 route_name='admin_settings_sessions_cleanup', request_method='POST')
178 202
179 203 config.add_route(
180 204 name='admin_settings_process_management',
181 205 pattern='/settings/process_management')
182 206 config.add_view(
183 207 AdminProcessManagementView,
184 208 attr='process_management',
185 209 route_name='admin_settings_process_management', request_method='GET',
186 210 renderer='rhodecode:templates/admin/settings/settings.mako')
187 211
188 212 config.add_route(
189 213 name='admin_settings_process_management_data',
190 214 pattern='/settings/process_management/data')
191 215 config.add_view(
192 216 AdminProcessManagementView,
193 217 attr='process_management_data',
194 218 route_name='admin_settings_process_management_data', request_method='GET',
195 219 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
196 220
197 221 config.add_route(
198 222 name='admin_settings_process_management_signal',
199 223 pattern='/settings/process_management/signal')
200 224 config.add_view(
201 225 AdminProcessManagementView,
202 226 attr='process_management_signal',
203 227 route_name='admin_settings_process_management_signal',
204 228 request_method='POST', renderer='json_ext')
205 229
206 230 config.add_route(
207 231 name='admin_settings_process_management_master_signal',
208 232 pattern='/settings/process_management/master_signal')
209 233 config.add_view(
210 234 AdminProcessManagementView,
211 235 attr='process_management_master_signal',
212 236 route_name='admin_settings_process_management_master_signal',
213 237 request_method='POST', renderer='json_ext')
214 238
215 239 # default settings
216 240 config.add_route(
217 241 name='admin_defaults_repositories',
218 242 pattern='/defaults/repositories')
219 243 config.add_view(
220 244 AdminDefaultSettingsView,
221 245 attr='defaults_repository_show',
222 246 route_name='admin_defaults_repositories', request_method='GET',
223 247 renderer='rhodecode:templates/admin/defaults/defaults.mako')
224 248
225 249 config.add_route(
226 250 name='admin_defaults_repositories_update',
227 251 pattern='/defaults/repositories/update')
228 252 config.add_view(
229 253 AdminDefaultSettingsView,
230 254 attr='defaults_repository_update',
231 255 route_name='admin_defaults_repositories_update', request_method='POST',
232 256 renderer='rhodecode:templates/admin/defaults/defaults.mako')
233 257
234 258 # admin settings
235 259
236 260 config.add_route(
237 261 name='admin_settings',
238 262 pattern='/settings')
239 263 config.add_view(
240 264 AdminSettingsView,
241 265 attr='settings_global',
242 266 route_name='admin_settings', request_method='GET',
243 267 renderer='rhodecode:templates/admin/settings/settings.mako')
244 268
245 269 config.add_route(
246 270 name='admin_settings_update',
247 271 pattern='/settings/update')
248 272 config.add_view(
249 273 AdminSettingsView,
250 274 attr='settings_global_update',
251 275 route_name='admin_settings_update', request_method='POST',
252 276 renderer='rhodecode:templates/admin/settings/settings.mako')
253 277
254 278 config.add_route(
255 279 name='admin_settings_global',
256 280 pattern='/settings/global')
257 281 config.add_view(
258 282 AdminSettingsView,
259 283 attr='settings_global',
260 284 route_name='admin_settings_global', request_method='GET',
261 285 renderer='rhodecode:templates/admin/settings/settings.mako')
262 286
263 287 config.add_route(
264 288 name='admin_settings_global_update',
265 289 pattern='/settings/global/update')
266 290 config.add_view(
267 291 AdminSettingsView,
268 292 attr='settings_global_update',
269 293 route_name='admin_settings_global_update', request_method='POST',
270 294 renderer='rhodecode:templates/admin/settings/settings.mako')
271 295
272 296 config.add_route(
273 297 name='admin_settings_vcs',
274 298 pattern='/settings/vcs')
275 299 config.add_view(
276 300 AdminSettingsView,
277 301 attr='settings_vcs',
278 302 route_name='admin_settings_vcs', request_method='GET',
279 303 renderer='rhodecode:templates/admin/settings/settings.mako')
280 304
281 305 config.add_route(
282 306 name='admin_settings_vcs_update',
283 307 pattern='/settings/vcs/update')
284 308 config.add_view(
285 309 AdminSettingsView,
286 310 attr='settings_vcs_update',
287 311 route_name='admin_settings_vcs_update', request_method='POST',
288 312 renderer='rhodecode:templates/admin/settings/settings.mako')
289 313
290 314 config.add_route(
291 315 name='admin_settings_vcs_svn_pattern_delete',
292 316 pattern='/settings/vcs/svn_pattern_delete')
293 317 config.add_view(
294 318 AdminSettingsView,
295 319 attr='settings_vcs_delete_svn_pattern',
296 320 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
297 321 renderer='json_ext', xhr=True)
298 322
299 323 config.add_route(
300 324 name='admin_settings_mapping',
301 325 pattern='/settings/mapping')
302 326 config.add_view(
303 327 AdminSettingsView,
304 328 attr='settings_mapping',
305 329 route_name='admin_settings_mapping', request_method='GET',
306 330 renderer='rhodecode:templates/admin/settings/settings.mako')
307 331
308 332 config.add_route(
309 333 name='admin_settings_mapping_update',
310 334 pattern='/settings/mapping/update')
311 335 config.add_view(
312 336 AdminSettingsView,
313 337 attr='settings_mapping_update',
314 338 route_name='admin_settings_mapping_update', request_method='POST',
315 339 renderer='rhodecode:templates/admin/settings/settings.mako')
316 340
317 341 config.add_route(
318 342 name='admin_settings_visual',
319 343 pattern='/settings/visual')
320 344 config.add_view(
321 345 AdminSettingsView,
322 346 attr='settings_visual',
323 347 route_name='admin_settings_visual', request_method='GET',
324 348 renderer='rhodecode:templates/admin/settings/settings.mako')
325 349
326 350 config.add_route(
327 351 name='admin_settings_visual_update',
328 352 pattern='/settings/visual/update')
329 353 config.add_view(
330 354 AdminSettingsView,
331 355 attr='settings_visual_update',
332 356 route_name='admin_settings_visual_update', request_method='POST',
333 357 renderer='rhodecode:templates/admin/settings/settings.mako')
334 358
335 359 config.add_route(
336 360 name='admin_settings_issuetracker',
337 361 pattern='/settings/issue-tracker')
338 362 config.add_view(
339 363 AdminSettingsView,
340 364 attr='settings_issuetracker',
341 365 route_name='admin_settings_issuetracker', request_method='GET',
342 366 renderer='rhodecode:templates/admin/settings/settings.mako')
343 367
344 368 config.add_route(
345 369 name='admin_settings_issuetracker_update',
346 370 pattern='/settings/issue-tracker/update')
347 371 config.add_view(
348 372 AdminSettingsView,
349 373 attr='settings_issuetracker_update',
350 374 route_name='admin_settings_issuetracker_update', request_method='POST',
351 375 renderer='rhodecode:templates/admin/settings/settings.mako')
352 376
353 377 config.add_route(
354 378 name='admin_settings_issuetracker_test',
355 379 pattern='/settings/issue-tracker/test')
356 380 config.add_view(
357 381 AdminSettingsView,
358 382 attr='settings_issuetracker_test',
359 383 route_name='admin_settings_issuetracker_test', request_method='POST',
360 384 renderer='string', xhr=True)
361 385
362 386 config.add_route(
363 387 name='admin_settings_issuetracker_delete',
364 388 pattern='/settings/issue-tracker/delete')
365 389 config.add_view(
366 390 AdminSettingsView,
367 391 attr='settings_issuetracker_delete',
368 392 route_name='admin_settings_issuetracker_delete', request_method='POST',
369 393 renderer='json_ext', xhr=True)
370 394
371 395 config.add_route(
372 396 name='admin_settings_email',
373 397 pattern='/settings/email')
374 398 config.add_view(
375 399 AdminSettingsView,
376 400 attr='settings_email',
377 401 route_name='admin_settings_email', request_method='GET',
378 402 renderer='rhodecode:templates/admin/settings/settings.mako')
379 403
380 404 config.add_route(
381 405 name='admin_settings_email_update',
382 406 pattern='/settings/email/update')
383 407 config.add_view(
384 408 AdminSettingsView,
385 409 attr='settings_email_update',
386 410 route_name='admin_settings_email_update', request_method='POST',
387 411 renderer='rhodecode:templates/admin/settings/settings.mako')
388 412
389 413 config.add_route(
390 414 name='admin_settings_hooks',
391 415 pattern='/settings/hooks')
392 416 config.add_view(
393 417 AdminSettingsView,
394 418 attr='settings_hooks',
395 419 route_name='admin_settings_hooks', request_method='GET',
396 420 renderer='rhodecode:templates/admin/settings/settings.mako')
397 421
398 422 config.add_route(
399 423 name='admin_settings_hooks_update',
400 424 pattern='/settings/hooks/update')
401 425 config.add_view(
402 426 AdminSettingsView,
403 427 attr='settings_hooks_update',
404 428 route_name='admin_settings_hooks_update', request_method='POST',
405 429 renderer='rhodecode:templates/admin/settings/settings.mako')
406 430
407 431 config.add_route(
408 432 name='admin_settings_hooks_delete',
409 433 pattern='/settings/hooks/delete')
410 434 config.add_view(
411 435 AdminSettingsView,
412 436 attr='settings_hooks_update',
413 437 route_name='admin_settings_hooks_delete', request_method='POST',
414 438 renderer='rhodecode:templates/admin/settings/settings.mako')
415 439
416 440 config.add_route(
417 441 name='admin_settings_search',
418 442 pattern='/settings/search')
419 443 config.add_view(
420 444 AdminSettingsView,
421 445 attr='settings_search',
422 446 route_name='admin_settings_search', request_method='GET',
423 447 renderer='rhodecode:templates/admin/settings/settings.mako')
424 448
425 449 config.add_route(
426 450 name='admin_settings_labs',
427 451 pattern='/settings/labs')
428 452 config.add_view(
429 453 AdminSettingsView,
430 454 attr='settings_labs',
431 455 route_name='admin_settings_labs', request_method='GET',
432 456 renderer='rhodecode:templates/admin/settings/settings.mako')
433 457
434 458 config.add_route(
435 459 name='admin_settings_labs_update',
436 460 pattern='/settings/labs/update')
437 461 config.add_view(
438 462 AdminSettingsView,
439 463 attr='settings_labs_update',
440 464 route_name='admin_settings_labs_update', request_method='POST',
441 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 467 # global permissions
454 468
455 469 config.add_route(
456 470 name='admin_permissions_application',
457 471 pattern='/permissions/application')
458 472 config.add_view(
459 473 AdminPermissionsView,
460 474 attr='permissions_application',
461 475 route_name='admin_permissions_application', request_method='GET',
462 476 renderer='rhodecode:templates/admin/permissions/permissions.mako')
463 477
464 478 config.add_route(
465 479 name='admin_permissions_application_update',
466 480 pattern='/permissions/application/update')
467 481 config.add_view(
468 482 AdminPermissionsView,
469 483 attr='permissions_application_update',
470 484 route_name='admin_permissions_application_update', request_method='POST',
471 485 renderer='rhodecode:templates/admin/permissions/permissions.mako')
472 486
473 487 config.add_route(
474 488 name='admin_permissions_global',
475 489 pattern='/permissions/global')
476 490 config.add_view(
477 491 AdminPermissionsView,
478 492 attr='permissions_global',
479 493 route_name='admin_permissions_global', request_method='GET',
480 494 renderer='rhodecode:templates/admin/permissions/permissions.mako')
481 495
482 496 config.add_route(
483 497 name='admin_permissions_global_update',
484 498 pattern='/permissions/global/update')
485 499 config.add_view(
486 500 AdminPermissionsView,
487 501 attr='permissions_global_update',
488 502 route_name='admin_permissions_global_update', request_method='POST',
489 503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
490 504
491 505 config.add_route(
492 506 name='admin_permissions_object',
493 507 pattern='/permissions/object')
494 508 config.add_view(
495 509 AdminPermissionsView,
496 510 attr='permissions_objects',
497 511 route_name='admin_permissions_object', request_method='GET',
498 512 renderer='rhodecode:templates/admin/permissions/permissions.mako')
499 513
500 514 config.add_route(
501 515 name='admin_permissions_object_update',
502 516 pattern='/permissions/object/update')
503 517 config.add_view(
504 518 AdminPermissionsView,
505 519 attr='permissions_objects_update',
506 520 route_name='admin_permissions_object_update', request_method='POST',
507 521 renderer='rhodecode:templates/admin/permissions/permissions.mako')
508 522
509 523 # Branch perms EE feature
510 524 config.add_route(
511 525 name='admin_permissions_branch',
512 526 pattern='/permissions/branch')
513 527 config.add_view(
514 528 AdminPermissionsView,
515 529 attr='permissions_branch',
516 530 route_name='admin_permissions_branch', request_method='GET',
517 531 renderer='rhodecode:templates/admin/permissions/permissions.mako')
518 532
519 533 config.add_route(
520 534 name='admin_permissions_ips',
521 535 pattern='/permissions/ips')
522 536 config.add_view(
523 537 AdminPermissionsView,
524 538 attr='permissions_ips',
525 539 route_name='admin_permissions_ips', request_method='GET',
526 540 renderer='rhodecode:templates/admin/permissions/permissions.mako')
527 541
528 542 config.add_route(
529 543 name='admin_permissions_overview',
530 544 pattern='/permissions/overview')
531 545 config.add_view(
532 546 AdminPermissionsView,
533 547 attr='permissions_overview',
534 548 route_name='admin_permissions_overview', request_method='GET',
535 549 renderer='rhodecode:templates/admin/permissions/permissions.mako')
536 550
537 551 config.add_route(
538 552 name='admin_permissions_auth_token_access',
539 553 pattern='/permissions/auth_token_access')
540 554 config.add_view(
541 555 AdminPermissionsView,
542 556 attr='auth_token_access',
543 557 route_name='admin_permissions_auth_token_access', request_method='GET',
544 558 renderer='rhodecode:templates/admin/permissions/permissions.mako')
545 559
546 560 config.add_route(
547 561 name='admin_permissions_ssh_keys',
548 562 pattern='/permissions/ssh_keys')
549 563 config.add_view(
550 564 AdminPermissionsView,
551 565 attr='ssh_keys',
552 566 route_name='admin_permissions_ssh_keys', request_method='GET',
553 567 renderer='rhodecode:templates/admin/permissions/permissions.mako')
554 568
555 569 config.add_route(
556 570 name='admin_permissions_ssh_keys_data',
557 571 pattern='/permissions/ssh_keys/data')
558 572 config.add_view(
559 573 AdminPermissionsView,
560 574 attr='ssh_keys_data',
561 575 route_name='admin_permissions_ssh_keys_data', request_method='GET',
562 576 renderer='json_ext', xhr=True)
563 577
564 578 config.add_route(
565 579 name='admin_permissions_ssh_keys_update',
566 580 pattern='/permissions/ssh_keys/update')
567 581 config.add_view(
568 582 AdminPermissionsView,
569 583 attr='ssh_keys_update',
570 584 route_name='admin_permissions_ssh_keys_update', request_method='POST',
571 585 renderer='rhodecode:templates/admin/permissions/permissions.mako')
572 586
573 587 # users admin
574 588 config.add_route(
575 589 name='users',
576 590 pattern='/users')
577 591 config.add_view(
578 592 AdminUsersView,
579 593 attr='users_list',
580 594 route_name='users', request_method='GET',
581 595 renderer='rhodecode:templates/admin/users/users.mako')
582 596
583 597 config.add_route(
584 598 name='users_data',
585 599 pattern='/users_data')
586 600 config.add_view(
587 601 AdminUsersView,
588 602 attr='users_list_data',
589 603 # renderer defined below
590 604 route_name='users_data', request_method='GET',
591 605 renderer='json_ext', xhr=True)
592 606
593 607 config.add_route(
594 608 name='users_create',
595 609 pattern='/users/create')
596 610 config.add_view(
597 611 AdminUsersView,
598 612 attr='users_create',
599 613 route_name='users_create', request_method='POST',
600 614 renderer='rhodecode:templates/admin/users/user_add.mako')
601 615
602 616 config.add_route(
603 617 name='users_new',
604 618 pattern='/users/new')
605 619 config.add_view(
606 620 AdminUsersView,
607 621 attr='users_new',
608 622 route_name='users_new', request_method='GET',
609 623 renderer='rhodecode:templates/admin/users/user_add.mako')
610 624
611 625 # user management
612 626 config.add_route(
613 627 name='user_edit',
614 628 pattern=r'/users/{user_id:\d+}/edit',
615 629 user_route=True)
616 630 config.add_view(
617 631 UsersView,
618 632 attr='user_edit',
619 633 route_name='user_edit', request_method='GET',
620 634 renderer='rhodecode:templates/admin/users/user_edit.mako')
621 635
622 636 config.add_route(
623 637 name='user_edit_advanced',
624 638 pattern=r'/users/{user_id:\d+}/edit/advanced',
625 639 user_route=True)
626 640 config.add_view(
627 641 UsersView,
628 642 attr='user_edit_advanced',
629 643 route_name='user_edit_advanced', request_method='GET',
630 644 renderer='rhodecode:templates/admin/users/user_edit.mako')
631 645
632 646 config.add_route(
633 647 name='user_edit_global_perms',
634 648 pattern=r'/users/{user_id:\d+}/edit/global_permissions',
635 649 user_route=True)
636 650 config.add_view(
637 651 UsersView,
638 652 attr='user_edit_global_perms',
639 653 route_name='user_edit_global_perms', request_method='GET',
640 654 renderer='rhodecode:templates/admin/users/user_edit.mako')
641 655
642 656 config.add_route(
643 657 name='user_edit_global_perms_update',
644 658 pattern=r'/users/{user_id:\d+}/edit/global_permissions/update',
645 659 user_route=True)
646 660 config.add_view(
647 661 UsersView,
648 662 attr='user_edit_global_perms_update',
649 663 route_name='user_edit_global_perms_update', request_method='POST',
650 664 renderer='rhodecode:templates/admin/users/user_edit.mako')
651 665
652 666 config.add_route(
653 667 name='user_update',
654 668 pattern=r'/users/{user_id:\d+}/update',
655 669 user_route=True)
656 670 config.add_view(
657 671 UsersView,
658 672 attr='user_update',
659 673 route_name='user_update', request_method='POST',
660 674 renderer='rhodecode:templates/admin/users/user_edit.mako')
661 675
662 676 config.add_route(
663 677 name='user_delete',
664 678 pattern=r'/users/{user_id:\d+}/delete',
665 679 user_route=True)
666 680 config.add_view(
667 681 UsersView,
668 682 attr='user_delete',
669 683 route_name='user_delete', request_method='POST',
670 684 renderer='rhodecode:templates/admin/users/user_edit.mako')
671 685
672 686 config.add_route(
673 687 name='user_enable_force_password_reset',
674 688 pattern=r'/users/{user_id:\d+}/password_reset_enable',
675 689 user_route=True)
676 690 config.add_view(
677 691 UsersView,
678 692 attr='user_enable_force_password_reset',
679 693 route_name='user_enable_force_password_reset', request_method='POST',
680 694 renderer='rhodecode:templates/admin/users/user_edit.mako')
681 695
682 696 config.add_route(
683 697 name='user_disable_force_password_reset',
684 698 pattern=r'/users/{user_id:\d+}/password_reset_disable',
685 699 user_route=True)
686 700 config.add_view(
687 701 UsersView,
688 702 attr='user_disable_force_password_reset',
689 703 route_name='user_disable_force_password_reset', request_method='POST',
690 704 renderer='rhodecode:templates/admin/users/user_edit.mako')
691 705
692 706 config.add_route(
693 707 name='user_create_personal_repo_group',
694 708 pattern=r'/users/{user_id:\d+}/create_repo_group',
695 709 user_route=True)
696 710 config.add_view(
697 711 UsersView,
698 712 attr='user_create_personal_repo_group',
699 713 route_name='user_create_personal_repo_group', request_method='POST',
700 714 renderer='rhodecode:templates/admin/users/user_edit.mako')
701 715
702 716 # user notice
703 717 config.add_route(
704 718 name='user_notice_dismiss',
705 719 pattern=r'/users/{user_id:\d+}/notice_dismiss',
706 720 user_route=True)
707 721 config.add_view(
708 722 UsersView,
709 723 attr='user_notice_dismiss',
710 724 route_name='user_notice_dismiss', request_method='POST',
711 725 renderer='json_ext', xhr=True)
712 726
713 727 # user auth tokens
714 728 config.add_route(
715 729 name='edit_user_auth_tokens',
716 730 pattern=r'/users/{user_id:\d+}/edit/auth_tokens',
717 731 user_route=True)
718 732 config.add_view(
719 733 UsersView,
720 734 attr='auth_tokens',
721 735 route_name='edit_user_auth_tokens', request_method='GET',
722 736 renderer='rhodecode:templates/admin/users/user_edit.mako')
723 737
724 738 config.add_route(
725 739 name='edit_user_auth_tokens_view',
726 740 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/view',
727 741 user_route=True)
728 742 config.add_view(
729 743 UsersView,
730 744 attr='auth_tokens_view',
731 745 route_name='edit_user_auth_tokens_view', request_method='POST',
732 746 renderer='json_ext', xhr=True)
733 747
734 748 config.add_route(
735 749 name='edit_user_auth_tokens_add',
736 750 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/new',
737 751 user_route=True)
738 752 config.add_view(
739 753 UsersView,
740 754 attr='auth_tokens_add',
741 755 route_name='edit_user_auth_tokens_add', request_method='POST')
742 756
743 757 config.add_route(
744 758 name='edit_user_auth_tokens_delete',
745 759 pattern=r'/users/{user_id:\d+}/edit/auth_tokens/delete',
746 760 user_route=True)
747 761 config.add_view(
748 762 UsersView,
749 763 attr='auth_tokens_delete',
750 764 route_name='edit_user_auth_tokens_delete', request_method='POST')
751 765
752 766 # user ssh keys
753 767 config.add_route(
754 768 name='edit_user_ssh_keys',
755 769 pattern=r'/users/{user_id:\d+}/edit/ssh_keys',
756 770 user_route=True)
757 771 config.add_view(
758 772 UsersView,
759 773 attr='ssh_keys',
760 774 route_name='edit_user_ssh_keys', request_method='GET',
761 775 renderer='rhodecode:templates/admin/users/user_edit.mako')
762 776
763 777 config.add_route(
764 778 name='edit_user_ssh_keys_generate_keypair',
765 779 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/generate',
766 780 user_route=True)
767 781 config.add_view(
768 782 UsersView,
769 783 attr='ssh_keys_generate_keypair',
770 784 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
771 785 renderer='rhodecode:templates/admin/users/user_edit.mako')
772 786
773 787 config.add_route(
774 788 name='edit_user_ssh_keys_add',
775 789 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/new',
776 790 user_route=True)
777 791 config.add_view(
778 792 UsersView,
779 793 attr='ssh_keys_add',
780 794 route_name='edit_user_ssh_keys_add', request_method='POST')
781 795
782 796 config.add_route(
783 797 name='edit_user_ssh_keys_delete',
784 798 pattern=r'/users/{user_id:\d+}/edit/ssh_keys/delete',
785 799 user_route=True)
786 800 config.add_view(
787 801 UsersView,
788 802 attr='ssh_keys_delete',
789 803 route_name='edit_user_ssh_keys_delete', request_method='POST')
790 804
791 805 # user emails
792 806 config.add_route(
793 807 name='edit_user_emails',
794 808 pattern=r'/users/{user_id:\d+}/edit/emails',
795 809 user_route=True)
796 810 config.add_view(
797 811 UsersView,
798 812 attr='emails',
799 813 route_name='edit_user_emails', request_method='GET',
800 814 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 815
802 816 config.add_route(
803 817 name='edit_user_emails_add',
804 818 pattern=r'/users/{user_id:\d+}/edit/emails/new',
805 819 user_route=True)
806 820 config.add_view(
807 821 UsersView,
808 822 attr='emails_add',
809 823 route_name='edit_user_emails_add', request_method='POST')
810 824
811 825 config.add_route(
812 826 name='edit_user_emails_delete',
813 827 pattern=r'/users/{user_id:\d+}/edit/emails/delete',
814 828 user_route=True)
815 829 config.add_view(
816 830 UsersView,
817 831 attr='emails_delete',
818 832 route_name='edit_user_emails_delete', request_method='POST')
819 833
820 834 # user IPs
821 835 config.add_route(
822 836 name='edit_user_ips',
823 837 pattern=r'/users/{user_id:\d+}/edit/ips',
824 838 user_route=True)
825 839 config.add_view(
826 840 UsersView,
827 841 attr='ips',
828 842 route_name='edit_user_ips', request_method='GET',
829 843 renderer='rhodecode:templates/admin/users/user_edit.mako')
830 844
831 845 config.add_route(
832 846 name='edit_user_ips_add',
833 847 pattern=r'/users/{user_id:\d+}/edit/ips/new',
834 848 user_route_with_default=True) # enabled for default user too
835 849 config.add_view(
836 850 UsersView,
837 851 attr='ips_add',
838 852 route_name='edit_user_ips_add', request_method='POST')
839 853
840 854 config.add_route(
841 855 name='edit_user_ips_delete',
842 856 pattern=r'/users/{user_id:\d+}/edit/ips/delete',
843 857 user_route_with_default=True) # enabled for default user too
844 858 config.add_view(
845 859 UsersView,
846 860 attr='ips_delete',
847 861 route_name='edit_user_ips_delete', request_method='POST')
848 862
849 863 # user perms
850 864 config.add_route(
851 865 name='edit_user_perms_summary',
852 866 pattern=r'/users/{user_id:\d+}/edit/permissions_summary',
853 867 user_route=True)
854 868 config.add_view(
855 869 UsersView,
856 870 attr='user_perms_summary',
857 871 route_name='edit_user_perms_summary', request_method='GET',
858 872 renderer='rhodecode:templates/admin/users/user_edit.mako')
859 873
860 874 config.add_route(
861 875 name='edit_user_perms_summary_json',
862 876 pattern=r'/users/{user_id:\d+}/edit/permissions_summary/json',
863 877 user_route=True)
864 878 config.add_view(
865 879 UsersView,
866 880 attr='user_perms_summary_json',
867 881 route_name='edit_user_perms_summary_json', request_method='GET',
868 882 renderer='json_ext')
869 883
870 884 # user user groups management
871 885 config.add_route(
872 886 name='edit_user_groups_management',
873 887 pattern=r'/users/{user_id:\d+}/edit/groups_management',
874 888 user_route=True)
875 889 config.add_view(
876 890 UsersView,
877 891 attr='groups_management',
878 892 route_name='edit_user_groups_management', request_method='GET',
879 893 renderer='rhodecode:templates/admin/users/user_edit.mako')
880 894
881 895 config.add_route(
882 896 name='edit_user_groups_management_updates',
883 897 pattern=r'/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
884 898 user_route=True)
885 899 config.add_view(
886 900 UsersView,
887 901 attr='groups_management_updates',
888 902 route_name='edit_user_groups_management_updates', request_method='POST')
889 903
890 904 # user audit logs
891 905 config.add_route(
892 906 name='edit_user_audit_logs',
893 907 pattern=r'/users/{user_id:\d+}/edit/audit', user_route=True)
894 908 config.add_view(
895 909 UsersView,
896 910 attr='user_audit_logs',
897 911 route_name='edit_user_audit_logs', request_method='GET',
898 912 renderer='rhodecode:templates/admin/users/user_edit.mako')
899 913
900 914 config.add_route(
901 915 name='edit_user_audit_logs_download',
902 916 pattern=r'/users/{user_id:\d+}/edit/audit/download', user_route=True)
903 917 config.add_view(
904 918 UsersView,
905 919 attr='user_audit_logs_download',
906 920 route_name='edit_user_audit_logs_download', request_method='GET',
907 921 renderer='string')
908 922
909 923 # user caches
910 924 config.add_route(
911 925 name='edit_user_caches',
912 926 pattern=r'/users/{user_id:\d+}/edit/caches',
913 927 user_route=True)
914 928 config.add_view(
915 929 UsersView,
916 930 attr='user_caches',
917 931 route_name='edit_user_caches', request_method='GET',
918 932 renderer='rhodecode:templates/admin/users/user_edit.mako')
919 933
920 934 config.add_route(
921 935 name='edit_user_caches_update',
922 936 pattern=r'/users/{user_id:\d+}/edit/caches/update',
923 937 user_route=True)
924 938 config.add_view(
925 939 UsersView,
926 940 attr='user_caches_update',
927 941 route_name='edit_user_caches_update', request_method='POST')
928 942
929 943 # user-groups admin
930 944 config.add_route(
931 945 name='user_groups',
932 946 pattern='/user_groups')
933 947 config.add_view(
934 948 AdminUserGroupsView,
935 949 attr='user_groups_list',
936 950 route_name='user_groups', request_method='GET',
937 951 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
938 952
939 953 config.add_route(
940 954 name='user_groups_data',
941 955 pattern='/user_groups_data')
942 956 config.add_view(
943 957 AdminUserGroupsView,
944 958 attr='user_groups_list_data',
945 959 route_name='user_groups_data', request_method='GET',
946 960 renderer='json_ext', xhr=True)
947 961
948 962 config.add_route(
949 963 name='user_groups_new',
950 964 pattern='/user_groups/new')
951 965 config.add_view(
952 966 AdminUserGroupsView,
953 967 attr='user_groups_new',
954 968 route_name='user_groups_new', request_method='GET',
955 969 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
956 970
957 971 config.add_route(
958 972 name='user_groups_create',
959 973 pattern='/user_groups/create')
960 974 config.add_view(
961 975 AdminUserGroupsView,
962 976 attr='user_groups_create',
963 977 route_name='user_groups_create', request_method='POST',
964 978 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
965 979
966 980 # repos admin
967 981 config.add_route(
968 982 name='repos',
969 983 pattern='/repos')
970 984 config.add_view(
971 985 AdminReposView,
972 986 attr='repository_list',
973 987 route_name='repos', request_method='GET',
974 988 renderer='rhodecode:templates/admin/repos/repos.mako')
975 989
976 990 config.add_route(
977 991 name='repos_data',
978 992 pattern='/repos_data')
979 993 config.add_view(
980 994 AdminReposView,
981 995 attr='repository_list_data',
982 996 route_name='repos_data', request_method='GET',
983 997 renderer='json_ext', xhr=True)
984 998
985 999 config.add_route(
986 1000 name='repo_new',
987 1001 pattern='/repos/new')
988 1002 config.add_view(
989 1003 AdminReposView,
990 1004 attr='repository_new',
991 1005 route_name='repo_new', request_method='GET',
992 1006 renderer='rhodecode:templates/admin/repos/repo_add.mako')
993 1007
994 1008 config.add_route(
995 1009 name='repo_create',
996 1010 pattern='/repos/create')
997 1011 config.add_view(
998 1012 AdminReposView,
999 1013 attr='repository_create',
1000 1014 route_name='repo_create', request_method='POST',
1001 1015 renderer='rhodecode:templates/admin/repos/repos.mako')
1002 1016
1003 1017 # repo groups admin
1004 1018 config.add_route(
1005 1019 name='repo_groups',
1006 1020 pattern='/repo_groups')
1007 1021 config.add_view(
1008 1022 AdminRepoGroupsView,
1009 1023 attr='repo_group_list',
1010 1024 route_name='repo_groups', request_method='GET',
1011 1025 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
1012 1026
1013 1027 config.add_route(
1014 1028 name='repo_groups_data',
1015 1029 pattern='/repo_groups_data')
1016 1030 config.add_view(
1017 1031 AdminRepoGroupsView,
1018 1032 attr='repo_group_list_data',
1019 1033 route_name='repo_groups_data', request_method='GET',
1020 1034 renderer='json_ext', xhr=True)
1021 1035
1022 1036 config.add_route(
1023 1037 name='repo_group_new',
1024 1038 pattern='/repo_group/new')
1025 1039 config.add_view(
1026 1040 AdminRepoGroupsView,
1027 1041 attr='repo_group_new',
1028 1042 route_name='repo_group_new', request_method='GET',
1029 1043 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1030 1044
1031 1045 config.add_route(
1032 1046 name='repo_group_create',
1033 1047 pattern='/repo_group/create')
1034 1048 config.add_view(
1035 1049 AdminRepoGroupsView,
1036 1050 attr='repo_group_create',
1037 1051 route_name='repo_group_create', request_method='POST',
1038 1052 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
1039 1053
1040 1054
1041 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 1056 # Create admin navigation registry and add it to the pyramid registry.
1046 1057 nav_includeme(config)
1047 1058
1048 1059 # main admin routes
1049 1060 config.add_route(
1050 1061 name='admin_home', pattern=ADMIN_PREFIX)
1051 1062 config.add_view(
1052 1063 AdminMainView,
1053 1064 attr='admin_main',
1054 1065 route_name='admin_home', request_method='GET',
1055 1066 renderer='rhodecode:templates/admin/main.mako')
1056 1067
1057 1068 # pr global redirect
1058 1069 config.add_route(
1059 1070 name='pull_requests_global_0', # backward compat
1060 1071 pattern=ADMIN_PREFIX + r'/pull_requests/{pull_request_id:\d+}')
1061 1072 config.add_view(
1062 1073 AdminMainView,
1063 1074 attr='pull_requests',
1064 1075 route_name='pull_requests_global_0', request_method='GET')
1065 1076
1066 1077 config.add_route(
1067 1078 name='pull_requests_global_1', # backward compat
1068 1079 pattern=ADMIN_PREFIX + r'/pull-requests/{pull_request_id:\d+}')
1069 1080 config.add_view(
1070 1081 AdminMainView,
1071 1082 attr='pull_requests',
1072 1083 route_name='pull_requests_global_1', request_method='GET')
1073 1084
1074 1085 config.add_route(
1075 1086 name='pull_requests_global',
1076 1087 pattern=ADMIN_PREFIX + r'/pull-request/{pull_request_id:\d+}')
1077 1088 config.add_view(
1078 1089 AdminMainView,
1079 1090 attr='pull_requests',
1080 1091 route_name='pull_requests_global', request_method='GET')
1081 1092
1082 1093 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
@@ -1,721 +1,714 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 import logging
21 21 import collections
22 22
23 23 import datetime
24 24 import formencode
25 25 import formencode.htmlfill
26 26
27 27 import rhodecode
28 28
29 29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 from rhodecode.apps._base import BaseAppView
34 34 from rhodecode.apps._base.navigation import navigation_list
35 35 from rhodecode.apps.svn_support.config_keys import generate_config
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import (
38 38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 39 from rhodecode.lib.celerylib import tasks, run_task
40 40 from rhodecode.lib.str_utils import safe_str
41 41 from rhodecode.lib.utils import repo2db_mapper
42 42 from rhodecode.lib.utils2 import str2bool, AttributeDict
43 43 from rhodecode.lib.index import searcher_from_config
44 44
45 45 from rhodecode.model.db import RhodeCodeUi, Repository
46 46 from rhodecode.model.forms import (ApplicationSettingsForm,
47 47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 48 LabsSettingsForm, IssueTrackerPatternsForm)
49 49 from rhodecode.model.permission import PermissionModel
50 50 from rhodecode.model.repo_group import RepoGroupModel
51 51
52 52 from rhodecode.model.scm import ScmModel
53 53 from rhodecode.model.notification import EmailNotificationModel
54 54 from rhodecode.model.meta import Session
55 55 from rhodecode.model.settings import (
56 56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 57 SettingsModel)
58 58
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class AdminSettingsView(BaseAppView):
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 c.labs_active = str2bool(
68 68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 69 c.navlist = navigation_list(self.request)
70 70 return c
71 71
72 72 @classmethod
73 73 def _get_ui_settings(cls):
74 74 ret = RhodeCodeUi.query().all()
75 75
76 76 if not ret:
77 77 raise Exception('Could not get application ui settings !')
78 78 settings = {}
79 79 for each in ret:
80 80 k = each.ui_key
81 81 v = each.ui_value
82 82 if k == '/':
83 83 k = 'root_path'
84 84
85 85 if k in ['push_ssl', 'publish', 'enabled']:
86 86 v = str2bool(v)
87 87
88 88 if k.find('.') != -1:
89 89 k = k.replace('.', '_')
90 90
91 91 if each.ui_section in ['hooks', 'extensions']:
92 92 v = each.ui_active
93 93
94 94 settings[each.ui_section + '_' + k] = v
95 95 return settings
96 96
97 97 @classmethod
98 98 def _form_defaults(cls):
99 99 defaults = SettingsModel().get_all_settings()
100 100 defaults.update(cls._get_ui_settings())
101 101
102 102 defaults.update({
103 103 'new_svn_branch': '',
104 104 'new_svn_tag': '',
105 105 })
106 106 return defaults
107 107
108 108 @LoginRequired()
109 109 @HasPermissionAllDecorator('hg.admin')
110 110 def settings_vcs(self):
111 111 c = self.load_default_context()
112 112 c.active = 'vcs'
113 113 model = VcsSettingsModel()
114 114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
115 115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
116 116
117 117 settings = self.request.registry.settings
118 118 c.svn_proxy_generate_config = settings[generate_config]
119 119
120 120 defaults = self._form_defaults()
121 121
122 122 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
123 123
124 124 data = render('rhodecode:templates/admin/settings/settings.mako',
125 125 self._get_template_context(c), self.request)
126 126 html = formencode.htmlfill.render(
127 127 data,
128 128 defaults=defaults,
129 129 encoding="UTF-8",
130 130 force_defaults=False
131 131 )
132 132 return Response(html)
133 133
134 134 @LoginRequired()
135 135 @HasPermissionAllDecorator('hg.admin')
136 136 @CSRFRequired()
137 137 def settings_vcs_update(self):
138 138 _ = self.request.translate
139 139 c = self.load_default_context()
140 140 c.active = 'vcs'
141 141
142 142 model = VcsSettingsModel()
143 143 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
144 144 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
145 145
146 146 settings = self.request.registry.settings
147 147 c.svn_proxy_generate_config = settings[generate_config]
148 148
149 149 application_form = ApplicationUiSettingsForm(self.request.translate)()
150 150
151 151 try:
152 152 form_result = application_form.to_python(dict(self.request.POST))
153 153 except formencode.Invalid as errors:
154 154 h.flash(
155 155 _("Some form inputs contain invalid data."),
156 156 category='error')
157 157 data = render('rhodecode:templates/admin/settings/settings.mako',
158 158 self._get_template_context(c), self.request)
159 159 html = formencode.htmlfill.render(
160 160 data,
161 161 defaults=errors.value,
162 162 errors=errors.unpack_errors() or {},
163 163 prefix_error=False,
164 164 encoding="UTF-8",
165 165 force_defaults=False
166 166 )
167 167 return Response(html)
168 168
169 169 try:
170 170 if c.visual.allow_repo_location_change:
171 171 model.update_global_path_setting(form_result['paths_root_path'])
172 172
173 173 model.update_global_ssl_setting(form_result['web_push_ssl'])
174 174 model.update_global_hook_settings(form_result)
175 175
176 176 model.create_or_update_global_svn_settings(form_result)
177 177 model.create_or_update_global_hg_settings(form_result)
178 178 model.create_or_update_global_git_settings(form_result)
179 179 model.create_or_update_global_pr_settings(form_result)
180 180 except Exception:
181 181 log.exception("Exception while updating settings")
182 182 h.flash(_('Error occurred during updating '
183 183 'application settings'), category='error')
184 184 else:
185 185 Session().commit()
186 186 h.flash(_('Updated VCS settings'), category='success')
187 187 raise HTTPFound(h.route_path('admin_settings_vcs'))
188 188
189 189 data = render('rhodecode:templates/admin/settings/settings.mako',
190 190 self._get_template_context(c), self.request)
191 191 html = formencode.htmlfill.render(
192 192 data,
193 193 defaults=self._form_defaults(),
194 194 encoding="UTF-8",
195 195 force_defaults=False
196 196 )
197 197 return Response(html)
198 198
199 199 @LoginRequired()
200 200 @HasPermissionAllDecorator('hg.admin')
201 201 @CSRFRequired()
202 202 def settings_vcs_delete_svn_pattern(self):
203 203 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
204 204 model = VcsSettingsModel()
205 205 try:
206 206 model.delete_global_svn_pattern(delete_pattern_id)
207 207 except SettingNotFound:
208 208 log.exception(
209 209 'Failed to delete svn_pattern with id %s', delete_pattern_id)
210 210 raise HTTPNotFound()
211 211
212 212 Session().commit()
213 213 return True
214 214
215 215 @LoginRequired()
216 216 @HasPermissionAllDecorator('hg.admin')
217 217 def settings_mapping(self):
218 218 c = self.load_default_context()
219 219 c.active = 'mapping'
220 220
221 221 data = render('rhodecode:templates/admin/settings/settings.mako',
222 222 self._get_template_context(c), self.request)
223 223 html = formencode.htmlfill.render(
224 224 data,
225 225 defaults=self._form_defaults(),
226 226 encoding="UTF-8",
227 227 force_defaults=False
228 228 )
229 229 return Response(html)
230 230
231 231 @LoginRequired()
232 232 @HasPermissionAllDecorator('hg.admin')
233 233 @CSRFRequired()
234 234 def settings_mapping_update(self):
235 235 _ = self.request.translate
236 236 c = self.load_default_context()
237 237 c.active = 'mapping'
238 238 rm_obsolete = self.request.POST.get('destroy', False)
239 239 invalidate_cache = self.request.POST.get('invalidate', False)
240 240 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
241 241
242 242 if invalidate_cache:
243 243 log.debug('invalidating all repositories cache')
244 244 for repo in Repository.get_all():
245 245 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
246 246
247 247 filesystem_repos = ScmModel().repo_scan()
248 248 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
249 249 PermissionModel().trigger_permission_flush()
250 250
251 def _repr(l):
252 return ', '.join(map(safe_str, l)) or '-'
251 def _repr(rm_repo):
252 return ', '.join(map(safe_str, rm_repo)) or '-'
253
253 254 h.flash(_('Repositories successfully '
254 255 'rescanned added: %s ; removed: %s') %
255 256 (_repr(added), _repr(removed)),
256 257 category='success')
257 258 raise HTTPFound(h.route_path('admin_settings_mapping'))
258 259
259 260 @LoginRequired()
260 261 @HasPermissionAllDecorator('hg.admin')
261 262 def settings_global(self):
262 263 c = self.load_default_context()
263 264 c.active = 'global'
264 265 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 266 .get_personal_group_name_pattern()
266 267
267 268 data = render('rhodecode:templates/admin/settings/settings.mako',
268 269 self._get_template_context(c), self.request)
269 270 html = formencode.htmlfill.render(
270 271 data,
271 272 defaults=self._form_defaults(),
272 273 encoding="UTF-8",
273 274 force_defaults=False
274 275 )
275 276 return Response(html)
276 277
277 278 @LoginRequired()
278 279 @HasPermissionAllDecorator('hg.admin')
279 280 @CSRFRequired()
280 281 def settings_global_update(self):
281 282 _ = self.request.translate
282 283 c = self.load_default_context()
283 284 c.active = 'global'
284 285 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 286 .get_personal_group_name_pattern()
286 287 application_form = ApplicationSettingsForm(self.request.translate)()
287 288 try:
288 289 form_result = application_form.to_python(dict(self.request.POST))
289 290 except formencode.Invalid as errors:
290 291 h.flash(
291 292 _("Some form inputs contain invalid data."),
292 293 category='error')
293 294 data = render('rhodecode:templates/admin/settings/settings.mako',
294 295 self._get_template_context(c), self.request)
295 296 html = formencode.htmlfill.render(
296 297 data,
297 298 defaults=errors.value,
298 299 errors=errors.unpack_errors() or {},
299 300 prefix_error=False,
300 301 encoding="UTF-8",
301 302 force_defaults=False
302 303 )
303 304 return Response(html)
304 305
305 306 settings = [
306 307 ('title', 'rhodecode_title', 'unicode'),
307 308 ('realm', 'rhodecode_realm', 'unicode'),
308 309 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 310 ('post_code', 'rhodecode_post_code', 'unicode'),
310 311 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 312 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 313 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 314 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 315 ]
315 316
316 317 try:
317 318 for setting, form_key, type_ in settings:
318 319 sett = SettingsModel().create_or_update_setting(
319 320 setting, form_result[form_key], type_)
320 321 Session().add(sett)
321 322
322 323 Session().commit()
323 324 SettingsModel().invalidate_settings_cache()
324 325 h.flash(_('Updated application settings'), category='success')
325 326 except Exception:
326 327 log.exception("Exception while updating application settings")
327 328 h.flash(
328 329 _('Error occurred during updating application settings'),
329 330 category='error')
330 331
331 332 raise HTTPFound(h.route_path('admin_settings_global'))
332 333
333 334 @LoginRequired()
334 335 @HasPermissionAllDecorator('hg.admin')
335 336 def settings_visual(self):
336 337 c = self.load_default_context()
337 338 c.active = 'visual'
338 339
339 340 data = render('rhodecode:templates/admin/settings/settings.mako',
340 341 self._get_template_context(c), self.request)
341 342 html = formencode.htmlfill.render(
342 343 data,
343 344 defaults=self._form_defaults(),
344 345 encoding="UTF-8",
345 346 force_defaults=False
346 347 )
347 348 return Response(html)
348 349
349 350 @LoginRequired()
350 351 @HasPermissionAllDecorator('hg.admin')
351 352 @CSRFRequired()
352 353 def settings_visual_update(self):
353 354 _ = self.request.translate
354 355 c = self.load_default_context()
355 356 c.active = 'visual'
356 357 application_form = ApplicationVisualisationForm(self.request.translate)()
357 358 try:
358 359 form_result = application_form.to_python(dict(self.request.POST))
359 360 except formencode.Invalid as errors:
360 361 h.flash(
361 362 _("Some form inputs contain invalid data."),
362 363 category='error')
363 364 data = render('rhodecode:templates/admin/settings/settings.mako',
364 365 self._get_template_context(c), self.request)
365 366 html = formencode.htmlfill.render(
366 367 data,
367 368 defaults=errors.value,
368 369 errors=errors.unpack_errors() or {},
369 370 prefix_error=False,
370 371 encoding="UTF-8",
371 372 force_defaults=False
372 373 )
373 374 return Response(html)
374 375
375 376 try:
376 377 settings = [
377 378 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
378 379 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
379 380 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
380 381 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
381 382 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
382 383 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
383 384 ('show_version', 'rhodecode_show_version', 'bool'),
384 385 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
385 386 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
386 387 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
387 388 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
388 389 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
389 390 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
390 391 ('support_url', 'rhodecode_support_url', 'unicode'),
391 392 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
392 393 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
393 394 ]
394 395 for setting, form_key, type_ in settings:
395 396 sett = SettingsModel().create_or_update_setting(
396 397 setting, form_result[form_key], type_)
397 398 Session().add(sett)
398 399
399 400 Session().commit()
400 401 SettingsModel().invalidate_settings_cache()
401 402 h.flash(_('Updated visualisation settings'), category='success')
402 403 except Exception:
403 404 log.exception("Exception updating visualization settings")
404 405 h.flash(_('Error occurred during updating '
405 406 'visualisation settings'),
406 407 category='error')
407 408
408 409 raise HTTPFound(h.route_path('admin_settings_visual'))
409 410
410 411 @LoginRequired()
411 412 @HasPermissionAllDecorator('hg.admin')
412 413 def settings_issuetracker(self):
413 414 c = self.load_default_context()
414 415 c.active = 'issuetracker'
415 416 defaults = c.rc_config
416 417
417 418 entry_key = 'rhodecode_issuetracker_pat_'
418 419
419 420 c.issuetracker_entries = {}
420 421 for k, v in defaults.items():
421 422 if k.startswith(entry_key):
422 423 uid = k[len(entry_key):]
423 424 c.issuetracker_entries[uid] = None
424 425
425 426 for uid in c.issuetracker_entries:
426 427 c.issuetracker_entries[uid] = AttributeDict({
427 428 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
428 429 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
429 430 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
430 431 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
431 432 })
432 433
433 434 return self._get_template_context(c)
434 435
435 436 @LoginRequired()
436 437 @HasPermissionAllDecorator('hg.admin')
437 438 @CSRFRequired()
438 439 def settings_issuetracker_test(self):
439 440 error_container = []
440 441
441 442 urlified_commit = h.urlify_commit_message(
442 443 self.request.POST.get('test_text', ''),
443 444 'repo_group/test_repo1', error_container=error_container)
444 445 if error_container:
445 446 def converter(inp):
446 447 return h.html_escape(inp)
447 448
448 449 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
449 450
450 451 return urlified_commit
451 452
452 453 @LoginRequired()
453 454 @HasPermissionAllDecorator('hg.admin')
454 455 @CSRFRequired()
455 456 def settings_issuetracker_update(self):
456 457 _ = self.request.translate
457 458 self.load_default_context()
458 459 settings_model = IssueTrackerSettingsModel()
459 460
460 461 try:
461 462 form = IssueTrackerPatternsForm(self.request.translate)()
462 463 data = form.to_python(self.request.POST)
463 464 except formencode.Invalid as errors:
464 465 log.exception('Failed to add new pattern')
465 466 error = errors
466 467 h.flash(_(f'Invalid issue tracker pattern: {error}'),
467 468 category='error')
468 469 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
469 470
470 471 if data:
471 472 for uid in data.get('delete_patterns', []):
472 473 settings_model.delete_entries(uid)
473 474
474 475 for pattern in data.get('patterns', []):
475 476 for setting, value, type_ in pattern:
476 477 sett = settings_model.create_or_update_setting(
477 478 setting, value, type_)
478 479 Session().add(sett)
479 480
480 481 Session().commit()
481 482
482 483 SettingsModel().invalidate_settings_cache()
483 484 h.flash(_('Updated issue tracker entries'), category='success')
484 485 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
485 486
486 487 @LoginRequired()
487 488 @HasPermissionAllDecorator('hg.admin')
488 489 @CSRFRequired()
489 490 def settings_issuetracker_delete(self):
490 491 _ = self.request.translate
491 492 self.load_default_context()
492 493 uid = self.request.POST.get('uid')
493 494 try:
494 495 IssueTrackerSettingsModel().delete_entries(uid)
495 496 except Exception:
496 497 log.exception('Failed to delete issue tracker setting %s', uid)
497 498 raise HTTPNotFound()
498 499
499 500 SettingsModel().invalidate_settings_cache()
500 501 h.flash(_('Removed issue tracker entry.'), category='success')
501 502
502 503 return {'deleted': uid}
503 504
504 505 @LoginRequired()
505 506 @HasPermissionAllDecorator('hg.admin')
506 507 def settings_email(self):
507 508 c = self.load_default_context()
508 509 c.active = 'email'
509 510 c.rhodecode_ini = rhodecode.CONFIG
510 511
511 512 data = render('rhodecode:templates/admin/settings/settings.mako',
512 513 self._get_template_context(c), self.request)
513 514 html = formencode.htmlfill.render(
514 515 data,
515 516 defaults=self._form_defaults(),
516 517 encoding="UTF-8",
517 518 force_defaults=False
518 519 )
519 520 return Response(html)
520 521
521 522 @LoginRequired()
522 523 @HasPermissionAllDecorator('hg.admin')
523 524 @CSRFRequired()
524 525 def settings_email_update(self):
525 526 _ = self.request.translate
526 527 c = self.load_default_context()
527 528 c.active = 'email'
528 529
529 530 test_email = self.request.POST.get('test_email')
530 531
531 532 if not test_email:
532 533 h.flash(_('Please enter email address'), category='error')
533 534 raise HTTPFound(h.route_path('admin_settings_email'))
534 535
535 536 email_kwargs = {
536 537 'date': datetime.datetime.now(),
537 538 'user': self._rhodecode_db_user
538 539 }
539 540
540 541 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
541 542 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
542 543
543 544 recipients = [test_email] if test_email else None
544 545
545 546 run_task(tasks.send_email, recipients, subject,
546 547 email_body_plaintext, email_body)
547 548
548 549 h.flash(_('Send email task created'), category='success')
549 550 raise HTTPFound(h.route_path('admin_settings_email'))
550 551
551 552 @LoginRequired()
552 553 @HasPermissionAllDecorator('hg.admin')
553 554 def settings_hooks(self):
554 555 c = self.load_default_context()
555 556 c.active = 'hooks'
556 557
557 558 model = SettingsModel()
558 559 c.hooks = model.get_builtin_hooks()
559 560 c.custom_hooks = model.get_custom_hooks()
560 561
561 562 data = render('rhodecode:templates/admin/settings/settings.mako',
562 563 self._get_template_context(c), self.request)
563 564 html = formencode.htmlfill.render(
564 565 data,
565 566 defaults=self._form_defaults(),
566 567 encoding="UTF-8",
567 568 force_defaults=False
568 569 )
569 570 return Response(html)
570 571
571 572 @LoginRequired()
572 573 @HasPermissionAllDecorator('hg.admin')
573 574 @CSRFRequired()
574 575 def settings_hooks_update(self):
575 576 _ = self.request.translate
576 577 c = self.load_default_context()
577 578 c.active = 'hooks'
578 579 if c.visual.allow_custom_hooks_settings:
579 580 ui_key = self.request.POST.get('new_hook_ui_key')
580 581 ui_value = self.request.POST.get('new_hook_ui_value')
581 582
582 583 hook_id = self.request.POST.get('hook_id')
583 584 new_hook = False
584 585
585 586 model = SettingsModel()
586 587 try:
587 588 if ui_value and ui_key:
588 589 model.create_or_update_hook(ui_key, ui_value)
589 590 h.flash(_('Added new hook'), category='success')
590 591 new_hook = True
591 592 elif hook_id:
592 593 RhodeCodeUi.delete(hook_id)
593 594 Session().commit()
594 595
595 596 # check for edits
596 597 update = False
597 598 _d = self.request.POST.dict_of_lists()
598 599 for k, v in zip(_d.get('hook_ui_key', []),
599 600 _d.get('hook_ui_value_new', [])):
600 601 model.create_or_update_hook(k, v)
601 602 update = True
602 603
603 604 if update and not new_hook:
604 605 h.flash(_('Updated hooks'), category='success')
605 606 Session().commit()
606 607 except Exception:
607 608 log.exception("Exception during hook creation")
608 609 h.flash(_('Error occurred during hook creation'),
609 610 category='error')
610 611
611 612 raise HTTPFound(h.route_path('admin_settings_hooks'))
612 613
613 614 @LoginRequired()
614 615 @HasPermissionAllDecorator('hg.admin')
615 616 def settings_search(self):
616 617 c = self.load_default_context()
617 618 c.active = 'search'
618 619
619 620 c.searcher = searcher_from_config(self.request.registry.settings)
620 621 c.statistics = c.searcher.statistics(self.request.translate)
621 622
622 623 return self._get_template_context(c)
623 624
624 625 @LoginRequired()
625 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 627 def settings_labs(self):
635 628 c = self.load_default_context()
636 629 if not c.labs_active:
637 630 raise HTTPFound(h.route_path('admin_settings'))
638 631
639 632 c.active = 'labs'
640 633 c.lab_settings = _LAB_SETTINGS
641 634
642 635 data = render('rhodecode:templates/admin/settings/settings.mako',
643 636 self._get_template_context(c), self.request)
644 637 html = formencode.htmlfill.render(
645 638 data,
646 639 defaults=self._form_defaults(),
647 640 encoding="UTF-8",
648 641 force_defaults=False
649 642 )
650 643 return Response(html)
651 644
652 645 @LoginRequired()
653 646 @HasPermissionAllDecorator('hg.admin')
654 647 @CSRFRequired()
655 648 def settings_labs_update(self):
656 649 _ = self.request.translate
657 650 c = self.load_default_context()
658 651 c.active = 'labs'
659 652
660 653 application_form = LabsSettingsForm(self.request.translate)()
661 654 try:
662 655 form_result = application_form.to_python(dict(self.request.POST))
663 656 except formencode.Invalid as errors:
664 657 h.flash(
665 658 _("Some form inputs contain invalid data."),
666 659 category='error')
667 660 data = render('rhodecode:templates/admin/settings/settings.mako',
668 661 self._get_template_context(c), self.request)
669 662 html = formencode.htmlfill.render(
670 663 data,
671 664 defaults=errors.value,
672 665 errors=errors.unpack_errors() or {},
673 666 prefix_error=False,
674 667 encoding="UTF-8",
675 668 force_defaults=False
676 669 )
677 670 return Response(html)
678 671
679 672 try:
680 673 session = Session()
681 674 for setting in _LAB_SETTINGS:
682 675 setting_name = setting.key[len('rhodecode_'):]
683 676 sett = SettingsModel().create_or_update_setting(
684 677 setting_name, form_result[setting.key], setting.type)
685 678 session.add(sett)
686 679
687 680 except Exception:
688 681 log.exception('Exception while updating lab settings')
689 682 h.flash(_('Error occurred during updating labs settings'),
690 683 category='error')
691 684 else:
692 685 Session().commit()
693 686 SettingsModel().invalidate_settings_cache()
694 687 h.flash(_('Updated Labs settings'), category='success')
695 688 raise HTTPFound(h.route_path('admin_settings_labs'))
696 689
697 690 data = render('rhodecode:templates/admin/settings/settings.mako',
698 691 self._get_template_context(c), self.request)
699 692 html = formencode.htmlfill.render(
700 693 data,
701 694 defaults=self._form_defaults(),
702 695 encoding="UTF-8",
703 696 force_defaults=False
704 697 )
705 698 return Response(html)
706 699
707 700
708 701 # :param key: name of the setting including the 'rhodecode_' prefix
709 702 # :param type: the RhodeCodeSetting type to use.
710 703 # :param group: the i18ned group in which we should dispaly this setting
711 704 # :param label: the i18ned label we should display for this setting
712 705 # :param help: the i18ned help we should dispaly for this setting
713 706 LabSetting = collections.namedtuple(
714 707 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
715 708
716 709
717 710 # This list has to be kept in sync with the form
718 711 # rhodecode.model.forms.LabsSettingsForm.
719 712 _LAB_SETTINGS = [
720 713
721 714 ]
@@ -1,1582 +1,1583 b''
1 1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import itertools
20 20 import logging
21 21 import os
22 22 import collections
23 23 import urllib.request
24 24 import urllib.parse
25 25 import urllib.error
26 26 import pathlib
27 27
28 28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 29
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 import rhodecode
34 34 from rhodecode.apps._base import RepoAppView
35 35
36 36
37 37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 38 from rhodecode.lib import audit_logger
39 39 from rhodecode.lib.hash_utils import sha1_safe
40 40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
41 41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
42 42 from rhodecode.lib.view_utils import parse_path_ref
43 43 from rhodecode.lib.exceptions import NonRelativePathError
44 44 from rhodecode.lib.codeblocks import (
45 45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
46 46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
47 47 from rhodecode.lib.type_utils import str2bool
48 48 from rhodecode.lib.str_utils import safe_str, safe_int
49 49 from rhodecode.lib.auth import (
50 50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
51 51 from rhodecode.lib.vcs import path as vcspath
52 52 from rhodecode.lib.vcs.backends.base import EmptyCommit
53 53 from rhodecode.lib.vcs.conf import settings
54 54 from rhodecode.lib.vcs.nodes import FileNode
55 55 from rhodecode.lib.vcs.exceptions import (
56 56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
57 57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
58 58 NodeDoesNotExistError, CommitError, NodeError)
59 59
60 60 from rhodecode.model.scm import ScmModel
61 61 from rhodecode.model.db import Repository
62 62
63 63 log = logging.getLogger(__name__)
64 64
65 65
66 66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
67 67 # original backward compat name of archive
68 68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
69 69
70 70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
71 71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
72 72 id_sha = sha1_safe(str(db_repo_id))[:4]
73 73 sub_repo = 'sub-1' if subrepos else 'sub-0'
74 74 commit = commit_sha if with_hash else 'archive'
75 75 path_marker = (path_sha if with_hash else '') or 'all'
76 76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
77 77
78 78 return archive_name
79 79
80 80
81 81 def get_path_sha(at_path):
82 82 return safe_str(sha1_safe(at_path)[:8])
83 83
84 84
85 85 def _get_archive_spec(fname):
86 86 log.debug('Detecting archive spec for: `%s`', fname)
87 87
88 88 fileformat = None
89 89 ext = None
90 90 content_type = None
91 91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
92 92
93 93 if fname.endswith(extension):
94 94 fileformat = a_type
95 95 log.debug('archive is of type: %s', fileformat)
96 96 ext = extension
97 97 break
98 98
99 99 if not fileformat:
100 100 raise ValueError()
101 101
102 102 # left over part of whole fname is the commit
103 103 commit_id = fname[:-len(ext)]
104 104
105 105 return commit_id, ext, fileformat, content_type
106 106
107 107
108 108 class RepoFilesView(RepoAppView):
109 109
110 110 @staticmethod
111 111 def adjust_file_path_for_svn(f_path, repo):
112 112 """
113 113 Computes the relative path of `f_path`.
114 114
115 115 This is mainly based on prefix matching of the recognized tags and
116 116 branches in the underlying repository.
117 117 """
118 118 tags_and_branches = itertools.chain(
119 119 repo.branches.keys(),
120 120 repo.tags.keys())
121 121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
122 122
123 123 for name in tags_and_branches:
124 124 if f_path.startswith(f'{name}/'):
125 125 f_path = vcspath.relpath(f_path, name)
126 126 break
127 127 return f_path
128 128
129 129 def load_default_context(self):
130 130 c = self._get_local_tmpl_context(include_app_defaults=True)
131 131 c.rhodecode_repo = self.rhodecode_vcs_repo
132 132 c.enable_downloads = self.db_repo.enable_downloads
133 133 return c
134 134
135 135 def _ensure_not_locked(self, commit_id='tip'):
136 136 _ = self.request.translate
137 137
138 138 repo = self.db_repo
139 139 if repo.enable_locking and repo.locked[0]:
140 140 h.flash(_('This repository has been locked by %s on %s')
141 141 % (h.person_by_id(repo.locked[0]),
142 142 h.format_date(h.time_to_datetime(repo.locked[1]))),
143 143 'warning')
144 144 files_url = h.route_path(
145 145 'repo_files:default_path',
146 146 repo_name=self.db_repo_name, commit_id=commit_id)
147 147 raise HTTPFound(files_url)
148 148
149 149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
150 150 _ = self.request.translate
151 151
152 152 if not is_head:
153 153 message = _('Cannot modify file. '
154 154 'Given commit `{}` is not head of a branch.').format(commit_id)
155 155 h.flash(message, category='warning')
156 156
157 157 if json_mode:
158 158 return message
159 159
160 160 files_url = h.route_path(
161 161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
162 162 f_path=f_path)
163 163 raise HTTPFound(files_url)
164 164
165 165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
166 166 _ = self.request.translate
167 167
168 168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
169 169 self.db_repo_name, branch_name)
170 170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
171 171 message = _('Branch `{}` changes forbidden by rule {}.').format(
172 172 h.escape(branch_name), h.escape(rule))
173 173 h.flash(message, 'warning')
174 174
175 175 if json_mode:
176 176 return message
177 177
178 178 files_url = h.route_path(
179 179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
180 180
181 181 raise HTTPFound(files_url)
182 182
183 183 def _get_commit_and_path(self):
184 184 default_commit_id = self.db_repo.landing_ref_name
185 185 default_f_path = '/'
186 186
187 187 commit_id = self.request.matchdict.get(
188 188 'commit_id', default_commit_id)
189 189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
190 190 return commit_id, f_path
191 191
192 192 def _get_default_encoding(self, c):
193 193 enc_list = getattr(c, 'default_encodings', [])
194 194 return enc_list[0] if enc_list else 'UTF-8'
195 195
196 196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
197 197 """
198 198 This is a safe way to get commit. If an error occurs it redirects to
199 199 tip with proper message
200 200
201 201 :param commit_id: id of commit to fetch
202 202 :param redirect_after: toggle redirection
203 203 """
204 204 _ = self.request.translate
205 205
206 206 try:
207 207 return self.rhodecode_vcs_repo.get_commit(commit_id)
208 208 except EmptyRepositoryError:
209 209 if not redirect_after:
210 210 return None
211 211
212 212 add_new = upload_new = ""
213 213 if h.HasRepoPermissionAny(
214 214 'repository.write', 'repository.admin')(self.db_repo_name):
215 215 _url = h.route_path(
216 216 'repo_files_add_file',
217 217 repo_name=self.db_repo_name, commit_id=0, f_path='')
218 218 add_new = h.link_to(
219 219 _('add a new file'), _url, class_="alert-link")
220 220
221 221 _url_upld = h.route_path(
222 222 'repo_files_upload_file',
223 223 repo_name=self.db_repo_name, commit_id=0, f_path='')
224 224 upload_new = h.link_to(
225 225 _('upload a new file'), _url_upld, class_="alert-link")
226 226
227 227 h.flash(h.literal(
228 228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
229 229 raise HTTPFound(
230 230 h.route_path('repo_summary', repo_name=self.db_repo_name))
231 231
232 232 except (CommitDoesNotExistError, LookupError) as e:
233 233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
234 234 h.flash(msg, category='error')
235 235 raise HTTPNotFound()
236 236 except RepositoryError as e:
237 237 h.flash(h.escape(safe_str(e)), category='error')
238 238 raise HTTPNotFound()
239 239
240 240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
241 241 """
242 242 Returns file_node, if error occurs or given path is directory,
243 243 it'll redirect to top level path
244 244 """
245 245 _ = self.request.translate
246 246
247 247 try:
248 248 file_node = commit_obj.get_node(path, pre_load=pre_load)
249 249 if file_node.is_dir():
250 250 raise RepositoryError('The given path is a directory')
251 251 except CommitDoesNotExistError:
252 252 log.exception('No such commit exists for this repository')
253 253 h.flash(_('No such commit exists for this repository'), category='error')
254 254 raise HTTPNotFound()
255 255 except RepositoryError as e:
256 256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
257 257 h.flash(h.escape(safe_str(e)), category='error')
258 258 raise HTTPNotFound()
259 259
260 260 return file_node
261 261
262 262 def _is_valid_head(self, commit_id, repo, landing_ref):
263 263 branch_name = sha_commit_id = ''
264 264 is_head = False
265 265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
266 266
267 267 for _branch_name, branch_commit_id in repo.branches.items():
268 268 # simple case we pass in branch name, it's a HEAD
269 269 if commit_id == _branch_name:
270 270 is_head = True
271 271 branch_name = _branch_name
272 272 sha_commit_id = branch_commit_id
273 273 break
274 274 # case when we pass in full sha commit_id, which is a head
275 275 elif commit_id == branch_commit_id:
276 276 is_head = True
277 277 branch_name = _branch_name
278 278 sha_commit_id = branch_commit_id
279 279 break
280 280
281 281 if h.is_svn(repo) and not repo.is_empty():
282 282 # Note: Subversion only has one head.
283 283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
284 284 is_head = True
285 285 return branch_name, sha_commit_id, is_head
286 286
287 287 # checked branches, means we only need to try to get the branch/commit_sha
288 288 if repo.is_empty():
289 289 is_head = True
290 290 branch_name = landing_ref
291 291 sha_commit_id = EmptyCommit().raw_id
292 292 else:
293 293 commit = repo.get_commit(commit_id=commit_id)
294 294 if commit:
295 295 branch_name = commit.branch
296 296 sha_commit_id = commit.raw_id
297 297
298 298 return branch_name, sha_commit_id, is_head
299 299
300 300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
301 301
302 302 repo_id = self.db_repo.repo_id
303 303 force_recache = self.get_recache_flag()
304 304
305 305 cache_seconds = safe_int(
306 306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
307 307 cache_on = not force_recache and cache_seconds > 0
308 308 log.debug(
309 309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
310 310 'with caching: %s[TTL: %ss]' % (
311 311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
312 312
313 313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
314 314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
315 315
316 316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
317 317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
318 318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
319 319 _repo_id, _commit_id, _f_path)
320 320
321 321 c.full_load = _full_load
322 322 return render(
323 323 'rhodecode:templates/files/files_browser_tree.mako',
324 324 self._get_template_context(c), self.request, _at_rev)
325 325
326 326 return compute_file_tree(
327 327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
328 328
329 329 def create_pure_path(self, *parts):
330 330 # Split paths and sanitize them, removing any ../ etc
331 331 sanitized_path = [
332 332 x for x in pathlib.PurePath(*parts).parts
333 333 if x not in ['.', '..']]
334 334
335 335 pure_path = pathlib.PurePath(*sanitized_path)
336 336 return pure_path
337 337
338 338 def _is_lf_enabled(self, target_repo):
339 339 lf_enabled = False
340 340
341 341 lf_key_for_vcs_map = {
342 342 'hg': 'extensions_largefiles',
343 343 'git': 'vcs_git_lfs_enabled'
344 344 }
345 345
346 346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
347 347
348 348 if lf_key_for_vcs:
349 349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
350 350
351 351 return lf_enabled
352 352
353 353 @LoginRequired()
354 354 @HasRepoPermissionAnyDecorator(
355 355 'repository.read', 'repository.write', 'repository.admin')
356 356 def repo_archivefile(self):
357 357 # archive cache config
358 358 from rhodecode import CONFIG
359 359 _ = self.request.translate
360 360 self.load_default_context()
361 361 default_at_path = '/'
362 362 fname = self.request.matchdict['fname']
363 363 subrepos = self.request.GET.get('subrepos') == 'true'
364 364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
365 365 at_path = self.request.GET.get('at_path') or default_at_path
366 366
367 367 if not self.db_repo.enable_downloads:
368 368 return Response(_('Downloads disabled'))
369 369
370 370 try:
371 371 commit_id, ext, fileformat, content_type = \
372 372 _get_archive_spec(fname)
373 373 except ValueError:
374 374 return Response(_('Unknown archive type for: `{}`').format(
375 375 h.escape(fname)))
376 376
377 377 try:
378 378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
379 379 except CommitDoesNotExistError:
380 380 return Response(_('Unknown commit_id {}').format(
381 381 h.escape(commit_id)))
382 382 except EmptyRepositoryError:
383 383 return Response(_('Empty repository'))
384 384
385 385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
386 386 if commit_id != commit.raw_id:
387 387 fname=f'{commit.raw_id}{ext}'
388 388 raise HTTPFound(self.request.current_route_path(fname=fname))
389 389
390 390 try:
391 391 at_path = commit.get_node(at_path).path or default_at_path
392 392 except Exception:
393 393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
394 394
395 395 path_sha = get_path_sha(at_path)
396 396
397 397 # used for cache etc, consistent unique archive name
398 398 archive_name_key = get_archive_name(
399 399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
400 400 path_sha=path_sha, with_hash=True)
401 401
402 402 if not with_hash:
403 403 path_sha = ''
404 404
405 405 # what end client gets served
406 406 response_archive_name = get_archive_name(
407 407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
408 408 path_sha=path_sha, with_hash=with_hash)
409 409
410 410 # remove extension from our archive directory name
411 411 archive_dir_name = response_archive_name[:-len(ext)]
412 412
413 413 archive_cache_disable = self.request.GET.get('no_cache')
414 414
415 415 d_cache = get_archival_cache_store(config=CONFIG)
416
416 417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 418 d_cache_conf = get_archival_config(config=CONFIG)
418 419
419 420 reentrant_lock_key = archive_name_key + '.lock'
420 421 with ReentrantLock(d_cache, reentrant_lock_key):
421 422 # This is also a cache key
422 423 use_cached_archive = False
423 424 if archive_name_key in d_cache and not archive_cache_disable:
424 425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 426 use_cached_archive = True
426 427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 428 archive_name_key, tag, reader.name)
428 429 else:
429 430 reader = None
430 431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431 432
432 433 # generate new archive, as previous was not found in the cache
433 434 if not reader:
434 435
435 436 try:
436 437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
437 438 kind=fileformat, subrepos=subrepos,
438 439 archive_at_path=at_path, cache_config=d_cache_conf)
439 440 except ImproperArchiveTypeError:
440 441 return _('Unknown archive type')
441 442
442 443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
443 444
444 445 if not reader:
445 446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
446 447
447 448 def archive_iterator(_reader, block_size: int = 4096*512):
448 449 # 4096 * 64 = 64KB
449 450 while 1:
450 451 data = _reader.read(block_size)
451 452 if not data:
452 453 break
453 454 yield data
454 455
455 456 response = Response(app_iter=archive_iterator(reader))
456 457 response.content_disposition = f'attachment; filename={response_archive_name}'
457 458 response.content_type = str(content_type)
458 459
459 460 try:
460 461 return response
461 462 finally:
462 463 # store download action
463 464 audit_logger.store_web(
464 465 'repo.archive.download', action_data={
465 466 'user_agent': self.request.user_agent,
466 467 'archive_name': archive_name_key,
467 468 'archive_spec': fname,
468 469 'archive_cached': use_cached_archive},
469 470 user=self._rhodecode_user,
470 471 repo=self.db_repo,
471 472 commit=True
472 473 )
473 474
474 475 def _get_file_node(self, commit_id, f_path):
475 476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
476 477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
477 478 try:
478 479 node = commit.get_node(f_path)
479 480 if node.is_dir():
480 481 raise NodeError(f'{node} path is a {type(node)} not a file')
481 482 except NodeDoesNotExistError:
482 483 commit = EmptyCommit(
483 484 commit_id=commit_id,
484 485 idx=commit.idx,
485 486 repo=commit.repository,
486 487 alias=commit.repository.alias,
487 488 message=commit.message,
488 489 author=commit.author,
489 490 date=commit.date)
490 491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
491 492 else:
492 493 commit = EmptyCommit(
493 494 repo=self.rhodecode_vcs_repo,
494 495 alias=self.rhodecode_vcs_repo.alias)
495 496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 497 return node
497 498
498 499 @LoginRequired()
499 500 @HasRepoPermissionAnyDecorator(
500 501 'repository.read', 'repository.write', 'repository.admin')
501 502 def repo_files_diff(self):
502 503 c = self.load_default_context()
503 504 f_path = self._get_f_path(self.request.matchdict)
504 505 diff1 = self.request.GET.get('diff1', '')
505 506 diff2 = self.request.GET.get('diff2', '')
506 507
507 508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
508 509
509 510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
510 511 line_context = self.request.GET.get('context', 3)
511 512
512 513 if not any((diff1, diff2)):
513 514 h.flash(
514 515 'Need query parameter "diff1" or "diff2" to generate a diff.',
515 516 category='error')
516 517 raise HTTPBadRequest()
517 518
518 519 c.action = self.request.GET.get('diff')
519 520 if c.action not in ['download', 'raw']:
520 521 compare_url = h.route_path(
521 522 'repo_compare',
522 523 repo_name=self.db_repo_name,
523 524 source_ref_type='rev',
524 525 source_ref=diff1,
525 526 target_repo=self.db_repo_name,
526 527 target_ref_type='rev',
527 528 target_ref=diff2,
528 529 _query=dict(f_path=f_path))
529 530 # redirect to new view if we render diff
530 531 raise HTTPFound(compare_url)
531 532
532 533 try:
533 534 node1 = self._get_file_node(diff1, path1)
534 535 node2 = self._get_file_node(diff2, f_path)
535 536 except (RepositoryError, NodeError):
536 537 log.exception("Exception while trying to get node from repository")
537 538 raise HTTPFound(
538 539 h.route_path('repo_files', repo_name=self.db_repo_name,
539 540 commit_id='tip', f_path=f_path))
540 541
541 542 if all(isinstance(node.commit, EmptyCommit)
542 543 for node in (node1, node2)):
543 544 raise HTTPNotFound()
544 545
545 546 c.commit_1 = node1.commit
546 547 c.commit_2 = node2.commit
547 548
548 549 if c.action == 'download':
549 550 _diff = diffs.get_gitdiff(node1, node2,
550 551 ignore_whitespace=ignore_whitespace,
551 552 context=line_context)
552 553 # NOTE: this was using diff_format='gitdiff'
553 554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
554 555
555 556 response = Response(self.path_filter.get_raw_patch(diff))
556 557 response.content_type = 'text/plain'
557 558 response.content_disposition = (
558 559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
559 560 )
560 561 charset = self._get_default_encoding(c)
561 562 if charset:
562 563 response.charset = charset
563 564 return response
564 565
565 566 elif c.action == 'raw':
566 567 _diff = diffs.get_gitdiff(node1, node2,
567 568 ignore_whitespace=ignore_whitespace,
568 569 context=line_context)
569 570 # NOTE: this was using diff_format='gitdiff'
570 571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
571 572
572 573 response = Response(self.path_filter.get_raw_patch(diff))
573 574 response.content_type = 'text/plain'
574 575 charset = self._get_default_encoding(c)
575 576 if charset:
576 577 response.charset = charset
577 578 return response
578 579
579 580 # in case we ever end up here
580 581 raise HTTPNotFound()
581 582
582 583 @LoginRequired()
583 584 @HasRepoPermissionAnyDecorator(
584 585 'repository.read', 'repository.write', 'repository.admin')
585 586 def repo_files_diff_2way_redirect(self):
586 587 """
587 588 Kept only to make OLD links work
588 589 """
589 590 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 591 diff1 = self.request.GET.get('diff1', '')
591 592 diff2 = self.request.GET.get('diff2', '')
592 593
593 594 if not any((diff1, diff2)):
594 595 h.flash(
595 596 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 597 category='error')
597 598 raise HTTPBadRequest()
598 599
599 600 compare_url = h.route_path(
600 601 'repo_compare',
601 602 repo_name=self.db_repo_name,
602 603 source_ref_type='rev',
603 604 source_ref=diff1,
604 605 target_ref_type='rev',
605 606 target_ref=diff2,
606 607 _query=dict(f_path=f_path, diffmode='sideside',
607 608 target_repo=self.db_repo_name,))
608 609 raise HTTPFound(compare_url)
609 610
610 611 @LoginRequired()
611 612 def repo_files_default_commit_redirect(self):
612 613 """
613 614 Special page that redirects to the landing page of files based on the default
614 615 commit for repository
615 616 """
616 617 c = self.load_default_context()
617 618 ref_name = c.rhodecode_db_repo.landing_ref_name
618 619 landing_url = h.repo_files_by_ref_url(
619 620 c.rhodecode_db_repo.repo_name,
620 621 c.rhodecode_db_repo.repo_type,
621 622 f_path='',
622 623 ref_name=ref_name,
623 624 commit_id='tip',
624 625 query=dict(at=ref_name)
625 626 )
626 627
627 628 raise HTTPFound(landing_url)
628 629
629 630 @LoginRequired()
630 631 @HasRepoPermissionAnyDecorator(
631 632 'repository.read', 'repository.write', 'repository.admin')
632 633 def repo_files(self):
633 634 c = self.load_default_context()
634 635
635 636 view_name = getattr(self.request.matched_route, 'name', None)
636 637
637 638 c.annotate = view_name == 'repo_files:annotated'
638 639 # default is false, but .rst/.md files later are auto rendered, we can
639 640 # overwrite auto rendering by setting this GET flag
640 641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
641 642
642 643 commit_id, f_path = self._get_commit_and_path()
643 644
644 645 c.commit = self._get_commit_or_redirect(commit_id)
645 646 c.branch = self.request.GET.get('branch', None)
646 647 c.f_path = f_path
647 648 at_rev = self.request.GET.get('at')
648 649
649 650 # prev link
650 651 try:
651 652 prev_commit = c.commit.prev(c.branch)
652 653 c.prev_commit = prev_commit
653 654 c.url_prev = h.route_path(
654 655 'repo_files', repo_name=self.db_repo_name,
655 656 commit_id=prev_commit.raw_id, f_path=f_path)
656 657 if c.branch:
657 658 c.url_prev += '?branch=%s' % c.branch
658 659 except (CommitDoesNotExistError, VCSError):
659 660 c.url_prev = '#'
660 661 c.prev_commit = EmptyCommit()
661 662
662 663 # next link
663 664 try:
664 665 next_commit = c.commit.next(c.branch)
665 666 c.next_commit = next_commit
666 667 c.url_next = h.route_path(
667 668 'repo_files', repo_name=self.db_repo_name,
668 669 commit_id=next_commit.raw_id, f_path=f_path)
669 670 if c.branch:
670 671 c.url_next += '?branch=%s' % c.branch
671 672 except (CommitDoesNotExistError, VCSError):
672 673 c.url_next = '#'
673 674 c.next_commit = EmptyCommit()
674 675
675 676 # files or dirs
676 677 try:
677 678 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
678 679
679 680 c.file_author = True
680 681 c.file_tree = ''
681 682
682 683 # load file content
683 684 if c.file.is_file():
684 685 c.lf_node = {}
685 686
686 687 has_lf_enabled = self._is_lf_enabled(self.db_repo)
687 688 if has_lf_enabled:
688 689 c.lf_node = c.file.get_largefile_node()
689 690
690 691 c.file_source_page = 'true'
691 692 c.file_last_commit = c.file.last_commit
692 693
693 694 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
694 695
695 696 if not (c.file_size_too_big or c.file.is_binary):
696 697 if c.annotate: # annotation has precedence over renderer
697 698 c.annotated_lines = filenode_as_annotated_lines_tokens(
698 699 c.file
699 700 )
700 701 else:
701 702 c.renderer = (
702 703 c.renderer and h.renderer_from_filename(c.file.path)
703 704 )
704 705 if not c.renderer:
705 706 c.lines = filenode_as_lines_tokens(c.file)
706 707
707 708 _branch_name, _sha_commit_id, is_head = \
708 709 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
709 710 landing_ref=self.db_repo.landing_ref_name)
710 711 c.on_branch_head = is_head
711 712
712 713 branch = c.commit.branch if (
713 714 c.commit.branch and '/' not in c.commit.branch) else None
714 715 c.branch_or_raw_id = branch or c.commit.raw_id
715 716 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
716 717
717 718 author = c.file_last_commit.author
718 719 c.authors = [[
719 720 h.email(author),
720 721 h.person(author, 'username_or_name_or_email'),
721 722 1
722 723 ]]
723 724
724 725 else: # load tree content at path
725 726 c.file_source_page = 'false'
726 727 c.authors = []
727 728 # this loads a simple tree without metadata to speed things up
728 729 # later via ajax we call repo_nodetree_full and fetch whole
729 730 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
730 731
731 732 c.readme_data, c.readme_file = \
732 733 self._get_readme_data(self.db_repo, c.visual.default_renderer,
733 734 c.commit.raw_id, f_path)
734 735
735 736 except RepositoryError as e:
736 737 h.flash(h.escape(safe_str(e)), category='error')
737 738 raise HTTPNotFound()
738 739
739 740 if self.request.environ.get('HTTP_X_PJAX'):
740 741 html = render('rhodecode:templates/files/files_pjax.mako',
741 742 self._get_template_context(c), self.request)
742 743 else:
743 744 html = render('rhodecode:templates/files/files.mako',
744 745 self._get_template_context(c), self.request)
745 746 return Response(html)
746 747
747 748 @HasRepoPermissionAnyDecorator(
748 749 'repository.read', 'repository.write', 'repository.admin')
749 750 def repo_files_annotated_previous(self):
750 751 self.load_default_context()
751 752
752 753 commit_id, f_path = self._get_commit_and_path()
753 754 commit = self._get_commit_or_redirect(commit_id)
754 755 prev_commit_id = commit.raw_id
755 756 line_anchor = self.request.GET.get('line_anchor')
756 757 is_file = False
757 758 try:
758 759 _file = commit.get_node(f_path)
759 760 is_file = _file.is_file()
760 761 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
761 762 pass
762 763
763 764 if is_file:
764 765 history = commit.get_path_history(f_path)
765 766 prev_commit_id = history[1].raw_id \
766 767 if len(history) > 1 else prev_commit_id
767 768 prev_url = h.route_path(
768 769 'repo_files:annotated', repo_name=self.db_repo_name,
769 770 commit_id=prev_commit_id, f_path=f_path,
770 771 _anchor=f'L{line_anchor}')
771 772
772 773 raise HTTPFound(prev_url)
773 774
774 775 @LoginRequired()
775 776 @HasRepoPermissionAnyDecorator(
776 777 'repository.read', 'repository.write', 'repository.admin')
777 778 def repo_nodetree_full(self):
778 779 """
779 780 Returns rendered html of file tree that contains commit date,
780 781 author, commit_id for the specified combination of
781 782 repo, commit_id and file path
782 783 """
783 784 c = self.load_default_context()
784 785
785 786 commit_id, f_path = self._get_commit_and_path()
786 787 commit = self._get_commit_or_redirect(commit_id)
787 788 try:
788 789 dir_node = commit.get_node(f_path)
789 790 except RepositoryError as e:
790 791 return Response(f'error: {h.escape(safe_str(e))}')
791 792
792 793 if dir_node.is_file():
793 794 return Response('')
794 795
795 796 c.file = dir_node
796 797 c.commit = commit
797 798 at_rev = self.request.GET.get('at')
798 799
799 800 html = self._get_tree_at_commit(
800 801 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
801 802
802 803 return Response(html)
803 804
804 805 def _get_attachement_headers(self, f_path):
805 806 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
806 807 safe_path = f_name.replace('"', '\\"')
807 808 encoded_path = urllib.parse.quote(f_name)
808 809
809 810 return "attachment; " \
810 811 "filename=\"{}\"; " \
811 812 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
812 813
813 814 @LoginRequired()
814 815 @HasRepoPermissionAnyDecorator(
815 816 'repository.read', 'repository.write', 'repository.admin')
816 817 def repo_file_raw(self):
817 818 """
818 819 Action for show as raw, some mimetypes are "rendered",
819 820 those include images, icons.
820 821 """
821 822 c = self.load_default_context()
822 823
823 824 commit_id, f_path = self._get_commit_and_path()
824 825 commit = self._get_commit_or_redirect(commit_id)
825 826 file_node = self._get_filenode_or_redirect(commit, f_path)
826 827
827 828 raw_mimetype_mapping = {
828 829 # map original mimetype to a mimetype used for "show as raw"
829 830 # you can also provide a content-disposition to override the
830 831 # default "attachment" disposition.
831 832 # orig_type: (new_type, new_dispo)
832 833
833 834 # show images inline:
834 835 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
835 836 # for example render an SVG with javascript inside or even render
836 837 # HTML.
837 838 'image/x-icon': ('image/x-icon', 'inline'),
838 839 'image/png': ('image/png', 'inline'),
839 840 'image/gif': ('image/gif', 'inline'),
840 841 'image/jpeg': ('image/jpeg', 'inline'),
841 842 'application/pdf': ('application/pdf', 'inline'),
842 843 }
843 844
844 845 mimetype = file_node.mimetype
845 846 try:
846 847 mimetype, disposition = raw_mimetype_mapping[mimetype]
847 848 except KeyError:
848 849 # we don't know anything special about this, handle it safely
849 850 if file_node.is_binary:
850 851 # do same as download raw for binary files
851 852 mimetype, disposition = 'application/octet-stream', 'attachment'
852 853 else:
853 854 # do not just use the original mimetype, but force text/plain,
854 855 # otherwise it would serve text/html and that might be unsafe.
855 856 # Note: underlying vcs library fakes text/plain mimetype if the
856 857 # mimetype can not be determined and it thinks it is not
857 858 # binary.This might lead to erroneous text display in some
858 859 # cases, but helps in other cases, like with text files
859 860 # without extension.
860 861 mimetype, disposition = 'text/plain', 'inline'
861 862
862 863 if disposition == 'attachment':
863 864 disposition = self._get_attachement_headers(f_path)
864 865
865 866 stream_content = file_node.stream_bytes()
866 867
867 868 response = Response(app_iter=stream_content)
868 869 response.content_disposition = disposition
869 870 response.content_type = mimetype
870 871
871 872 charset = self._get_default_encoding(c)
872 873 if charset:
873 874 response.charset = charset
874 875
875 876 return response
876 877
877 878 @LoginRequired()
878 879 @HasRepoPermissionAnyDecorator(
879 880 'repository.read', 'repository.write', 'repository.admin')
880 881 def repo_file_download(self):
881 882 c = self.load_default_context()
882 883
883 884 commit_id, f_path = self._get_commit_and_path()
884 885 commit = self._get_commit_or_redirect(commit_id)
885 886 file_node = self._get_filenode_or_redirect(commit, f_path)
886 887
887 888 if self.request.GET.get('lf'):
888 889 # only if lf get flag is passed, we download this file
889 890 # as LFS/Largefile
890 891 lf_node = file_node.get_largefile_node()
891 892 if lf_node:
892 893 # overwrite our pointer with the REAL large-file
893 894 file_node = lf_node
894 895
895 896 disposition = self._get_attachement_headers(f_path)
896 897
897 898 stream_content = file_node.stream_bytes()
898 899
899 900 response = Response(app_iter=stream_content)
900 901 response.content_disposition = disposition
901 902 response.content_type = file_node.mimetype
902 903
903 904 charset = self._get_default_encoding(c)
904 905 if charset:
905 906 response.charset = charset
906 907
907 908 return response
908 909
909 910 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
910 911
911 912 cache_seconds = safe_int(
912 913 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
913 914 cache_on = cache_seconds > 0
914 915 log.debug(
915 916 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
916 917 'with caching: %s[TTL: %ss]' % (
917 918 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
918 919
919 920 cache_namespace_uid = f'repo.{repo_id}'
920 921 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
921 922
922 923 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
923 924 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
924 925 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
925 926 _repo_id, commit_id, f_path)
926 927 try:
927 928 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
928 929 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
929 930 log.exception(safe_str(e))
930 931 h.flash(h.escape(safe_str(e)), category='error')
931 932 raise HTTPFound(h.route_path(
932 933 'repo_files', repo_name=self.db_repo_name,
933 934 commit_id='tip', f_path='/'))
934 935
935 936 return _d + _f
936 937
937 938 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
938 939 commit_id, f_path)
939 940 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
940 941
941 942 @LoginRequired()
942 943 @HasRepoPermissionAnyDecorator(
943 944 'repository.read', 'repository.write', 'repository.admin')
944 945 def repo_nodelist(self):
945 946 self.load_default_context()
946 947
947 948 commit_id, f_path = self._get_commit_and_path()
948 949 commit = self._get_commit_or_redirect(commit_id)
949 950
950 951 metadata = self._get_nodelist_at_commit(
951 952 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
952 953 return {'nodes': [x for x in metadata]}
953 954
954 955 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
955 956 items = []
956 957 for name, commit_id in branches_or_tags.items():
957 958 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
958 959 items.append((sym_ref, name, ref_type))
959 960 return items
960 961
961 962 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
962 963 return commit_id
963 964
964 965 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
965 966 return commit_id
966 967
967 968 # NOTE(dan): old code we used in "diff" mode compare
968 969 new_f_path = vcspath.join(name, f_path)
969 970 return f'{new_f_path}@{commit_id}'
970 971
971 972 def _get_node_history(self, commit_obj, f_path, commits=None):
972 973 """
973 974 get commit history for given node
974 975
975 976 :param commit_obj: commit to calculate history
976 977 :param f_path: path for node to calculate history for
977 978 :param commits: if passed don't calculate history and take
978 979 commits defined in this list
979 980 """
980 981 _ = self.request.translate
981 982
982 983 # calculate history based on tip
983 984 tip = self.rhodecode_vcs_repo.get_commit()
984 985 if commits is None:
985 986 pre_load = ["author", "branch"]
986 987 try:
987 988 commits = tip.get_path_history(f_path, pre_load=pre_load)
988 989 except (NodeDoesNotExistError, CommitError):
989 990 # this node is not present at tip!
990 991 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
991 992
992 993 history = []
993 994 commits_group = ([], _("Changesets"))
994 995 for commit in commits:
995 996 branch = ' (%s)' % commit.branch if commit.branch else ''
996 997 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
997 998 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
998 999 history.append(commits_group)
999 1000
1000 1001 symbolic_reference = self._symbolic_reference
1001 1002
1002 1003 if self.rhodecode_vcs_repo.alias == 'svn':
1003 1004 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1004 1005 f_path, self.rhodecode_vcs_repo)
1005 1006 if adjusted_f_path != f_path:
1006 1007 log.debug(
1007 1008 'Recognized svn tag or branch in file "%s", using svn '
1008 1009 'specific symbolic references', f_path)
1009 1010 f_path = adjusted_f_path
1010 1011 symbolic_reference = self._symbolic_reference_svn
1011 1012
1012 1013 branches = self._create_references(
1013 1014 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1014 1015 branches_group = (branches, _("Branches"))
1015 1016
1016 1017 tags = self._create_references(
1017 1018 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1018 1019 tags_group = (tags, _("Tags"))
1019 1020
1020 1021 history.append(branches_group)
1021 1022 history.append(tags_group)
1022 1023
1023 1024 return history, commits
1024 1025
1025 1026 @LoginRequired()
1026 1027 @HasRepoPermissionAnyDecorator(
1027 1028 'repository.read', 'repository.write', 'repository.admin')
1028 1029 def repo_file_history(self):
1029 1030 self.load_default_context()
1030 1031
1031 1032 commit_id, f_path = self._get_commit_and_path()
1032 1033 commit = self._get_commit_or_redirect(commit_id)
1033 1034 file_node = self._get_filenode_or_redirect(commit, f_path)
1034 1035
1035 1036 if file_node.is_file():
1036 1037 file_history, _hist = self._get_node_history(commit, f_path)
1037 1038
1038 1039 res = []
1039 1040 for section_items, section in file_history:
1040 1041 items = []
1041 1042 for obj_id, obj_text, obj_type in section_items:
1042 1043 at_rev = ''
1043 1044 if obj_type in ['branch', 'bookmark', 'tag']:
1044 1045 at_rev = obj_text
1045 1046 entry = {
1046 1047 'id': obj_id,
1047 1048 'text': obj_text,
1048 1049 'type': obj_type,
1049 1050 'at_rev': at_rev
1050 1051 }
1051 1052
1052 1053 items.append(entry)
1053 1054
1054 1055 res.append({
1055 1056 'text': section,
1056 1057 'children': items
1057 1058 })
1058 1059
1059 1060 data = {
1060 1061 'more': False,
1061 1062 'results': res
1062 1063 }
1063 1064 return data
1064 1065
1065 1066 log.warning('Cannot fetch history for directory')
1066 1067 raise HTTPBadRequest()
1067 1068
1068 1069 @LoginRequired()
1069 1070 @HasRepoPermissionAnyDecorator(
1070 1071 'repository.read', 'repository.write', 'repository.admin')
1071 1072 def repo_file_authors(self):
1072 1073 c = self.load_default_context()
1073 1074
1074 1075 commit_id, f_path = self._get_commit_and_path()
1075 1076 commit = self._get_commit_or_redirect(commit_id)
1076 1077 file_node = self._get_filenode_or_redirect(commit, f_path)
1077 1078
1078 1079 if not file_node.is_file():
1079 1080 raise HTTPBadRequest()
1080 1081
1081 1082 c.file_last_commit = file_node.last_commit
1082 1083 if self.request.GET.get('annotate') == '1':
1083 1084 # use _hist from annotation if annotation mode is on
1084 1085 commit_ids = {x[1] for x in file_node.annotate}
1085 1086 _hist = (
1086 1087 self.rhodecode_vcs_repo.get_commit(commit_id)
1087 1088 for commit_id in commit_ids)
1088 1089 else:
1089 1090 _f_history, _hist = self._get_node_history(commit, f_path)
1090 1091 c.file_author = False
1091 1092
1092 1093 unique = collections.OrderedDict()
1093 1094 for commit in _hist:
1094 1095 author = commit.author
1095 1096 if author not in unique:
1096 1097 unique[commit.author] = [
1097 1098 h.email(author),
1098 1099 h.person(author, 'username_or_name_or_email'),
1099 1100 1 # counter
1100 1101 ]
1101 1102
1102 1103 else:
1103 1104 # increase counter
1104 1105 unique[commit.author][2] += 1
1105 1106
1106 1107 c.authors = [val for val in unique.values()]
1107 1108
1108 1109 return self._get_template_context(c)
1109 1110
1110 1111 @LoginRequired()
1111 1112 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1112 1113 def repo_files_check_head(self):
1113 1114 self.load_default_context()
1114 1115
1115 1116 commit_id, f_path = self._get_commit_and_path()
1116 1117 _branch_name, _sha_commit_id, is_head = \
1117 1118 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1118 1119 landing_ref=self.db_repo.landing_ref_name)
1119 1120
1120 1121 new_path = self.request.POST.get('path')
1121 1122 operation = self.request.POST.get('operation')
1122 1123 path_exist = ''
1123 1124
1124 1125 if new_path and operation in ['create', 'upload']:
1125 1126 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1126 1127 try:
1127 1128 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1128 1129 # NOTE(dan): construct whole path without leading /
1129 1130 file_node = commit_obj.get_node(new_f_path)
1130 1131 if file_node is not None:
1131 1132 path_exist = new_f_path
1132 1133 except EmptyRepositoryError:
1133 1134 pass
1134 1135 except Exception:
1135 1136 pass
1136 1137
1137 1138 return {
1138 1139 'branch': _branch_name,
1139 1140 'sha': _sha_commit_id,
1140 1141 'is_head': is_head,
1141 1142 'path_exists': path_exist
1142 1143 }
1143 1144
1144 1145 @LoginRequired()
1145 1146 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 1147 def repo_files_remove_file(self):
1147 1148 _ = self.request.translate
1148 1149 c = self.load_default_context()
1149 1150 commit_id, f_path = self._get_commit_and_path()
1150 1151
1151 1152 self._ensure_not_locked()
1152 1153 _branch_name, _sha_commit_id, is_head = \
1153 1154 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1154 1155 landing_ref=self.db_repo.landing_ref_name)
1155 1156
1156 1157 self.forbid_non_head(is_head, f_path)
1157 1158 self.check_branch_permission(_branch_name)
1158 1159
1159 1160 c.commit = self._get_commit_or_redirect(commit_id)
1160 1161 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1161 1162
1162 1163 c.default_message = _(
1163 1164 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1164 1165 c.f_path = f_path
1165 1166
1166 1167 return self._get_template_context(c)
1167 1168
1168 1169 @LoginRequired()
1169 1170 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 1171 @CSRFRequired()
1171 1172 def repo_files_delete_file(self):
1172 1173 _ = self.request.translate
1173 1174
1174 1175 c = self.load_default_context()
1175 1176 commit_id, f_path = self._get_commit_and_path()
1176 1177
1177 1178 self._ensure_not_locked()
1178 1179 _branch_name, _sha_commit_id, is_head = \
1179 1180 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1180 1181 landing_ref=self.db_repo.landing_ref_name)
1181 1182
1182 1183 self.forbid_non_head(is_head, f_path)
1183 1184 self.check_branch_permission(_branch_name)
1184 1185
1185 1186 c.commit = self._get_commit_or_redirect(commit_id)
1186 1187 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1187 1188
1188 1189 c.default_message = _(
1189 1190 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1190 1191 c.f_path = f_path
1191 1192 node_path = f_path
1192 1193 author = self._rhodecode_db_user.full_contact
1193 1194 message = self.request.POST.get('message') or c.default_message
1194 1195 try:
1195 1196 nodes = {
1196 1197 safe_bytes(node_path): {
1197 1198 'content': b''
1198 1199 }
1199 1200 }
1200 1201 ScmModel().delete_nodes(
1201 1202 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1202 1203 message=message,
1203 1204 nodes=nodes,
1204 1205 parent_commit=c.commit,
1205 1206 author=author,
1206 1207 )
1207 1208
1208 1209 h.flash(
1209 1210 _('Successfully deleted file `{}`').format(
1210 1211 h.escape(f_path)), category='success')
1211 1212 except Exception:
1212 1213 log.exception('Error during commit operation')
1213 1214 h.flash(_('Error occurred during commit'), category='error')
1214 1215 raise HTTPFound(
1215 1216 h.route_path('repo_commit', repo_name=self.db_repo_name,
1216 1217 commit_id='tip'))
1217 1218
1218 1219 @LoginRequired()
1219 1220 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1220 1221 def repo_files_edit_file(self):
1221 1222 _ = self.request.translate
1222 1223 c = self.load_default_context()
1223 1224 commit_id, f_path = self._get_commit_and_path()
1224 1225
1225 1226 self._ensure_not_locked()
1226 1227 _branch_name, _sha_commit_id, is_head = \
1227 1228 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1228 1229 landing_ref=self.db_repo.landing_ref_name)
1229 1230
1230 1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232 1233
1233 1234 c.commit = self._get_commit_or_redirect(commit_id)
1234 1235 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1235 1236
1236 1237 if c.file.is_binary:
1237 1238 files_url = h.route_path(
1238 1239 'repo_files',
1239 1240 repo_name=self.db_repo_name,
1240 1241 commit_id=c.commit.raw_id, f_path=f_path)
1241 1242 raise HTTPFound(files_url)
1242 1243
1243 1244 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1244 1245 c.f_path = f_path
1245 1246
1246 1247 return self._get_template_context(c)
1247 1248
1248 1249 @LoginRequired()
1249 1250 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1250 1251 @CSRFRequired()
1251 1252 def repo_files_update_file(self):
1252 1253 _ = self.request.translate
1253 1254 c = self.load_default_context()
1254 1255 commit_id, f_path = self._get_commit_and_path()
1255 1256
1256 1257 self._ensure_not_locked()
1257 1258
1258 1259 c.commit = self._get_commit_or_redirect(commit_id)
1259 1260 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1260 1261
1261 1262 if c.file.is_binary:
1262 1263 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1263 1264 commit_id=c.commit.raw_id, f_path=f_path))
1264 1265
1265 1266 _branch_name, _sha_commit_id, is_head = \
1266 1267 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1267 1268 landing_ref=self.db_repo.landing_ref_name)
1268 1269
1269 1270 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1270 1271 self.check_branch_permission(_branch_name, commit_id=commit_id)
1271 1272
1272 1273 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1273 1274 c.f_path = f_path
1274 1275
1275 1276 old_content = c.file.str_content
1276 1277 sl = old_content.splitlines(1)
1277 1278 first_line = sl[0] if sl else ''
1278 1279
1279 1280 r_post = self.request.POST
1280 1281 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1281 1282 line_ending_mode = detect_mode(first_line, 0)
1282 1283 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1283 1284
1284 1285 message = r_post.get('message') or c.default_message
1285 1286
1286 1287 org_node_path = c.file.str_path
1287 1288 filename = r_post['filename']
1288 1289
1289 1290 root_path = c.file.dir_path
1290 1291 pure_path = self.create_pure_path(root_path, filename)
1291 1292 node_path = pure_path.as_posix()
1292 1293
1293 1294 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1294 1295 commit_id=commit_id)
1295 1296 if content == old_content and node_path == org_node_path:
1296 1297 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1297 1298 category='warning')
1298 1299 raise HTTPFound(default_redirect_url)
1299 1300
1300 1301 try:
1301 1302 mapping = {
1302 1303 c.file.bytes_path: {
1303 1304 'org_filename': org_node_path,
1304 1305 'filename': safe_bytes(node_path),
1305 1306 'content': safe_bytes(content),
1306 1307 'lexer': '',
1307 1308 'op': 'mod',
1308 1309 'mode': c.file.mode
1309 1310 }
1310 1311 }
1311 1312
1312 1313 commit = ScmModel().update_nodes(
1313 1314 user=self._rhodecode_db_user.user_id,
1314 1315 repo=self.db_repo,
1315 1316 message=message,
1316 1317 nodes=mapping,
1317 1318 parent_commit=c.commit,
1318 1319 )
1319 1320
1320 1321 h.flash(_('Successfully committed changes to file `{}`').format(
1321 1322 h.escape(f_path)), category='success')
1322 1323 default_redirect_url = h.route_path(
1323 1324 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1324 1325
1325 1326 except Exception:
1326 1327 log.exception('Error occurred during commit')
1327 1328 h.flash(_('Error occurred during commit'), category='error')
1328 1329
1329 1330 raise HTTPFound(default_redirect_url)
1330 1331
1331 1332 @LoginRequired()
1332 1333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1333 1334 def repo_files_add_file(self):
1334 1335 _ = self.request.translate
1335 1336 c = self.load_default_context()
1336 1337 commit_id, f_path = self._get_commit_and_path()
1337 1338
1338 1339 self._ensure_not_locked()
1339 1340
1340 1341 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1341 1342 if c.commit is None:
1342 1343 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1343 1344
1344 1345 if self.rhodecode_vcs_repo.is_empty():
1345 1346 # for empty repository we cannot check for current branch, we rely on
1346 1347 # c.commit.branch instead
1347 1348 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1348 1349 else:
1349 1350 _branch_name, _sha_commit_id, is_head = \
1350 1351 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1351 1352 landing_ref=self.db_repo.landing_ref_name)
1352 1353
1353 1354 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1354 1355 self.check_branch_permission(_branch_name, commit_id=commit_id)
1355 1356
1356 1357 c.default_message = (_('Added file via RhodeCode Enterprise'))
1357 1358 c.f_path = f_path.lstrip('/') # ensure not relative path
1358 1359
1359 1360 return self._get_template_context(c)
1360 1361
1361 1362 @LoginRequired()
1362 1363 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1363 1364 @CSRFRequired()
1364 1365 def repo_files_create_file(self):
1365 1366 _ = self.request.translate
1366 1367 c = self.load_default_context()
1367 1368 commit_id, f_path = self._get_commit_and_path()
1368 1369
1369 1370 self._ensure_not_locked()
1370 1371
1371 1372 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1372 1373 if c.commit is None:
1373 1374 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1374 1375
1375 1376 # calculate redirect URL
1376 1377 if self.rhodecode_vcs_repo.is_empty():
1377 1378 default_redirect_url = h.route_path(
1378 1379 'repo_summary', repo_name=self.db_repo_name)
1379 1380 else:
1380 1381 default_redirect_url = h.route_path(
1381 1382 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1382 1383
1383 1384 if self.rhodecode_vcs_repo.is_empty():
1384 1385 # for empty repository we cannot check for current branch, we rely on
1385 1386 # c.commit.branch instead
1386 1387 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1387 1388 else:
1388 1389 _branch_name, _sha_commit_id, is_head = \
1389 1390 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1390 1391 landing_ref=self.db_repo.landing_ref_name)
1391 1392
1392 1393 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1393 1394 self.check_branch_permission(_branch_name, commit_id=commit_id)
1394 1395
1395 1396 c.default_message = (_('Added file via RhodeCode Enterprise'))
1396 1397 c.f_path = f_path
1397 1398
1398 1399 r_post = self.request.POST
1399 1400 message = r_post.get('message') or c.default_message
1400 1401 filename = r_post.get('filename')
1401 1402 unix_mode = 0
1402 1403
1403 1404 if not filename:
1404 1405 # If there's no commit, redirect to repo summary
1405 1406 if type(c.commit) is EmptyCommit:
1406 1407 redirect_url = h.route_path(
1407 1408 'repo_summary', repo_name=self.db_repo_name)
1408 1409 else:
1409 1410 redirect_url = default_redirect_url
1410 1411 h.flash(_('No filename specified'), category='warning')
1411 1412 raise HTTPFound(redirect_url)
1412 1413
1413 1414 root_path = f_path
1414 1415 pure_path = self.create_pure_path(root_path, filename)
1415 1416 node_path = pure_path.as_posix().lstrip('/')
1416 1417
1417 1418 author = self._rhodecode_db_user.full_contact
1418 1419 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1419 1420 nodes = {
1420 1421 safe_bytes(node_path): {
1421 1422 'content': safe_bytes(content)
1422 1423 }
1423 1424 }
1424 1425
1425 1426 try:
1426 1427
1427 1428 commit = ScmModel().create_nodes(
1428 1429 user=self._rhodecode_db_user.user_id,
1429 1430 repo=self.db_repo,
1430 1431 message=message,
1431 1432 nodes=nodes,
1432 1433 parent_commit=c.commit,
1433 1434 author=author,
1434 1435 )
1435 1436
1436 1437 h.flash(_('Successfully committed new file `{}`').format(
1437 1438 h.escape(node_path)), category='success')
1438 1439
1439 1440 default_redirect_url = h.route_path(
1440 1441 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1441 1442
1442 1443 except NonRelativePathError:
1443 1444 log.exception('Non Relative path found')
1444 1445 h.flash(_('The location specified must be a relative path and must not '
1445 1446 'contain .. in the path'), category='warning')
1446 1447 raise HTTPFound(default_redirect_url)
1447 1448 except (NodeError, NodeAlreadyExistsError) as e:
1448 1449 h.flash(h.escape(safe_str(e)), category='error')
1449 1450 except Exception:
1450 1451 log.exception('Error occurred during commit')
1451 1452 h.flash(_('Error occurred during commit'), category='error')
1452 1453
1453 1454 raise HTTPFound(default_redirect_url)
1454 1455
1455 1456 @LoginRequired()
1456 1457 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1457 1458 @CSRFRequired()
1458 1459 def repo_files_upload_file(self):
1459 1460 _ = self.request.translate
1460 1461 c = self.load_default_context()
1461 1462 commit_id, f_path = self._get_commit_and_path()
1462 1463
1463 1464 self._ensure_not_locked()
1464 1465
1465 1466 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1466 1467 if c.commit is None:
1467 1468 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1468 1469
1469 1470 # calculate redirect URL
1470 1471 if self.rhodecode_vcs_repo.is_empty():
1471 1472 default_redirect_url = h.route_path(
1472 1473 'repo_summary', repo_name=self.db_repo_name)
1473 1474 else:
1474 1475 default_redirect_url = h.route_path(
1475 1476 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1476 1477
1477 1478 if self.rhodecode_vcs_repo.is_empty():
1478 1479 # for empty repository we cannot check for current branch, we rely on
1479 1480 # c.commit.branch instead
1480 1481 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1481 1482 else:
1482 1483 _branch_name, _sha_commit_id, is_head = \
1483 1484 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1484 1485 landing_ref=self.db_repo.landing_ref_name)
1485 1486
1486 1487 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1487 1488 if error:
1488 1489 return {
1489 1490 'error': error,
1490 1491 'redirect_url': default_redirect_url
1491 1492 }
1492 1493 error = self.check_branch_permission(_branch_name, json_mode=True)
1493 1494 if error:
1494 1495 return {
1495 1496 'error': error,
1496 1497 'redirect_url': default_redirect_url
1497 1498 }
1498 1499
1499 1500 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1500 1501 c.f_path = f_path
1501 1502
1502 1503 r_post = self.request.POST
1503 1504
1504 1505 message = c.default_message
1505 1506 user_message = r_post.getall('message')
1506 1507 if isinstance(user_message, list) and user_message:
1507 1508 # we take the first from duplicated results if it's not empty
1508 1509 message = user_message[0] if user_message[0] else message
1509 1510
1510 1511 nodes = {}
1511 1512
1512 1513 for file_obj in r_post.getall('files_upload') or []:
1513 1514 content = file_obj.file
1514 1515 filename = file_obj.filename
1515 1516
1516 1517 root_path = f_path
1517 1518 pure_path = self.create_pure_path(root_path, filename)
1518 1519 node_path = pure_path.as_posix().lstrip('/')
1519 1520
1520 1521 nodes[safe_bytes(node_path)] = {
1521 1522 'content': content
1522 1523 }
1523 1524
1524 1525 if not nodes:
1525 1526 error = 'missing files'
1526 1527 return {
1527 1528 'error': error,
1528 1529 'redirect_url': default_redirect_url
1529 1530 }
1530 1531
1531 1532 author = self._rhodecode_db_user.full_contact
1532 1533
1533 1534 try:
1534 1535 commit = ScmModel().create_nodes(
1535 1536 user=self._rhodecode_db_user.user_id,
1536 1537 repo=self.db_repo,
1537 1538 message=message,
1538 1539 nodes=nodes,
1539 1540 parent_commit=c.commit,
1540 1541 author=author,
1541 1542 )
1542 1543 if len(nodes) == 1:
1543 1544 flash_message = _('Successfully committed {} new files').format(len(nodes))
1544 1545 else:
1545 1546 flash_message = _('Successfully committed 1 new file')
1546 1547
1547 1548 h.flash(flash_message, category='success')
1548 1549
1549 1550 default_redirect_url = h.route_path(
1550 1551 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1551 1552
1552 1553 except NonRelativePathError:
1553 1554 log.exception('Non Relative path found')
1554 1555 error = _('The location specified must be a relative path and must not '
1555 1556 'contain .. in the path')
1556 1557 h.flash(error, category='warning')
1557 1558
1558 1559 return {
1559 1560 'error': error,
1560 1561 'redirect_url': default_redirect_url
1561 1562 }
1562 1563 except (NodeError, NodeAlreadyExistsError) as e:
1563 1564 error = h.escape(e)
1564 1565 h.flash(error, category='error')
1565 1566
1566 1567 return {
1567 1568 'error': error,
1568 1569 'redirect_url': default_redirect_url
1569 1570 }
1570 1571 except Exception:
1571 1572 log.exception('Error occurred during commit')
1572 1573 error = _('Error occurred during commit')
1573 1574 h.flash(error, category='error')
1574 1575 return {
1575 1576 'error': error,
1576 1577 'redirect_url': default_redirect_url
1577 1578 }
1578 1579
1579 1580 return {
1580 1581 'error': None,
1581 1582 'redirect_url': default_redirect_url
1582 1583 }
@@ -1,203 +1,206 b''
1 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import logging
20 20
21 21 from rhodecode.translation import lazy_ugettext
22 22 from rhodecode.events.repo import (RepoEvent, _commits_as_dict, _issues_as_dict)
23 23
24 24 log = logging.getLogger(__name__)
25 25
26 26
27 27 class PullRequestEvent(RepoEvent):
28 28 """
29 29 Base class for pull request events.
30 30
31 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 37 def __init__(self, pullrequest):
35 38 super().__init__(pullrequest.target_repo)
36 39 self.pullrequest = pullrequest
37 40
38 41 def as_dict(self):
39 42 from rhodecode.lib.utils2 import md5_safe
40 43 from rhodecode.model.pull_request import PullRequestModel
41 44 data = super().as_dict()
42 45
43 46 commits = _commits_as_dict(
44 47 self,
45 48 commit_ids=self.pullrequest.revisions,
46 49 repos=[self.pullrequest.source_repo]
47 50 )
48 51 issues = _issues_as_dict(commits)
49 52 # calculate hashes of all commits for unique identifier of commits
50 53 # inside that pull request
51 54 commits_hash = md5_safe(':'.join(x.get('raw_id', '') for x in commits))
52 55
53 56 data.update({
54 57 'pullrequest': {
55 58 'title': self.pullrequest.title,
56 59 'issues': issues,
57 60 'pull_request_id': self.pullrequest.pull_request_id,
58 61 'url': PullRequestModel().get_url(
59 62 self.pullrequest, request=self.request),
60 63 'permalink_url': PullRequestModel().get_url(
61 64 self.pullrequest, request=self.request, permalink=True),
62 65 'shadow_url': PullRequestModel().get_shadow_clone_url(
63 66 self.pullrequest, request=self.request),
64 67 'status': self.pullrequest.calculated_review_status(),
65 68 'commits_uid': commits_hash,
66 69 'commits': commits,
67 70 }
68 71 })
69 72 return data
70 73
71 74
72 75 class PullRequestCreateEvent(PullRequestEvent):
73 76 """
74 77 An instance of this class is emitted as an :term:`event` after a pull
75 78 request is created.
76 79 """
77 80 name = 'pullrequest-create'
78 81 display_name = lazy_ugettext('pullrequest created')
79 82 description = lazy_ugettext('Event triggered after pull request was created')
80 83
81 84
82 85 class PullRequestCloseEvent(PullRequestEvent):
83 86 """
84 87 An instance of this class is emitted as an :term:`event` after a pull
85 88 request is closed.
86 89 """
87 90 name = 'pullrequest-close'
88 91 display_name = lazy_ugettext('pullrequest closed')
89 92 description = lazy_ugettext('Event triggered after pull request was closed')
90 93
91 94
92 95 class PullRequestUpdateEvent(PullRequestEvent):
93 96 """
94 97 An instance of this class is emitted as an :term:`event` after a pull
95 98 request's commits have been updated.
96 99 """
97 100 name = 'pullrequest-update'
98 101 display_name = lazy_ugettext('pullrequest commits updated')
99 102 description = lazy_ugettext('Event triggered after pull requests was updated')
100 103
101 104
102 105 class PullRequestReviewEvent(PullRequestEvent):
103 106 """
104 107 An instance of this class is emitted as an :term:`event` after a pull
105 108 request review has changed. A status defines new status of review.
106 109 """
107 110 name = 'pullrequest-review'
108 111 display_name = lazy_ugettext('pullrequest review changed')
109 112 description = lazy_ugettext('Event triggered after a review status of a '
110 113 'pull requests has changed to other.')
111 114
112 115 def __init__(self, pullrequest, status):
113 116 super().__init__(pullrequest)
114 117 self.status = status
115 118
116 119
117 120 class PullRequestMergeEvent(PullRequestEvent):
118 121 """
119 122 An instance of this class is emitted as an :term:`event` after a pull
120 123 request is merged.
121 124 """
122 125 name = 'pullrequest-merge'
123 126 display_name = lazy_ugettext('pullrequest merged')
124 127 description = lazy_ugettext('Event triggered after a successful merge operation '
125 128 'was executed on a pull request')
126 129
127 130
128 131 class PullRequestCommentEvent(PullRequestEvent):
129 132 """
130 133 An instance of this class is emitted as an :term:`event` after a pull
131 134 request comment is created.
132 135 """
133 136 name = 'pullrequest-comment'
134 137 display_name = lazy_ugettext('pullrequest commented')
135 138 description = lazy_ugettext('Event triggered after a comment was made on a code '
136 139 'in the pull request')
137 140
138 141 def __init__(self, pullrequest, comment):
139 142 super().__init__(pullrequest)
140 143 self.comment = comment
141 144
142 145 def as_dict(self):
143 146 from rhodecode.model.comment import CommentsModel
144 147 data = super().as_dict()
145 148
146 149 status = None
147 150 if self.comment.status_change:
148 151 status = self.comment.review_status
149 152
150 153 data.update({
151 154 'comment': {
152 155 'status': status,
153 156 'text': self.comment.text,
154 157 'type': self.comment.comment_type,
155 158 'file': self.comment.f_path,
156 159 'line': self.comment.line_no,
157 160 'version': self.comment.last_version,
158 161 'url': CommentsModel().get_url(
159 162 self.comment, request=self.request),
160 163 'permalink_url': CommentsModel().get_url(
161 164 self.comment, request=self.request, permalink=True),
162 165 }
163 166 })
164 167 return data
165 168
166 169
167 170 class PullRequestCommentEditEvent(PullRequestEvent):
168 171 """
169 172 An instance of this class is emitted as an :term:`event` after a pull
170 173 request comment is edited.
171 174 """
172 175 name = 'pullrequest-comment-edit'
173 176 display_name = lazy_ugettext('pullrequest comment edited')
174 177 description = lazy_ugettext('Event triggered after a comment was edited on a code '
175 178 'in the pull request')
176 179
177 180 def __init__(self, pullrequest, comment):
178 181 super().__init__(pullrequest)
179 182 self.comment = comment
180 183
181 184 def as_dict(self):
182 185 from rhodecode.model.comment import CommentsModel
183 186 data = super().as_dict()
184 187
185 188 status = None
186 189 if self.comment.status_change:
187 190 status = self.comment.review_status
188 191
189 192 data.update({
190 193 'comment': {
191 194 'status': status,
192 195 'text': self.comment.text,
193 196 'type': self.comment.comment_type,
194 197 'file': self.comment.f_path,
195 198 'line': self.comment.line_no,
196 199 'version': self.comment.last_version,
197 200 'url': CommentsModel().get_url(
198 201 self.comment, request=self.request),
199 202 'permalink_url': CommentsModel().get_url(
200 203 self.comment, request=self.request, permalink=True),
201 204 }
202 205 })
203 206 return data
@@ -1,440 +1,444 b''
1 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import collections
20 20 import logging
21 21 import datetime
22 22
23 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 25 from rhodecode.events.base import RhodeCodeIntegrationEvent
26 26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 def _commits_as_dict(event, commit_ids, repos):
32 32 """
33 33 Helper function to serialize commit_ids
34 34
35 35 :param event: class calling this method
36 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 39 from rhodecode.lib.utils2 import extract_mentioned_users
40 40 from rhodecode.lib.helpers import (
41 41 urlify_commit_message, process_patterns, chop_at_smart)
42 42 from rhodecode.model.repo import RepoModel
43 43
44 44 if not repos:
45 45 raise Exception('no repo defined')
46 46
47 47 if not isinstance(repos, (tuple, list)):
48 48 repos = [repos]
49 49
50 50 if not commit_ids:
51 51 return []
52 52
53 53 needed_commits = list(commit_ids)
54 54
55 55 commits = []
56 56 reviewers = []
57 57 for repo in repos:
58 58 if not needed_commits:
59 59 return commits # return early if we have the commits we need
60 60
61 61 vcs_repo = repo.scm_instance(cache=False)
62 62
63 63 try:
64 64 # use copy of needed_commits since we modify it while iterating
65 65 for commit_id in list(needed_commits):
66 66 if commit_id.startswith('tag=>'):
67 67 raw_id = commit_id[5:]
68 68 cs_data = {
69 69 'raw_id': commit_id, 'short_id': commit_id,
70 70 'branch': None,
71 71 'git_ref_change': 'tag_add',
72 72 'message': f'Added new tag {raw_id}',
73 73 'author': event.actor.full_contact,
74 74 'date': datetime.datetime.now(),
75 75 'refs': {
76 76 'branches': [],
77 77 'bookmarks': [],
78 78 'tags': []
79 79 }
80 80 }
81 81 commits.append(cs_data)
82 82
83 83 elif commit_id.startswith('delete_branch=>'):
84 84 raw_id = commit_id[15:]
85 85 cs_data = {
86 86 'raw_id': commit_id, 'short_id': commit_id,
87 87 'branch': None,
88 88 'git_ref_change': 'branch_delete',
89 89 'message': f'Deleted branch {raw_id}',
90 90 'author': event.actor.full_contact,
91 91 'date': datetime.datetime.now(),
92 92 'refs': {
93 93 'branches': [],
94 94 'bookmarks': [],
95 95 'tags': []
96 96 }
97 97 }
98 98 commits.append(cs_data)
99 99
100 100 else:
101 101 try:
102 102 cs = vcs_repo.get_commit(commit_id)
103 103 except CommitDoesNotExistError:
104 104 continue # maybe its in next repo
105 105
106 106 cs_data = cs.__json__()
107 107 cs_data['refs'] = cs._get_refs()
108 108
109 109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
110 110 cs_data['reviewers'] = reviewers
111 111 cs_data['url'] = RepoModel().get_commit_url(
112 112 repo, cs_data['raw_id'], request=event.request)
113 113 cs_data['permalink_url'] = RepoModel().get_commit_url(
114 114 repo, cs_data['raw_id'], request=event.request,
115 115 permalink=True)
116 116 urlified_message, issues_data, errors = process_patterns(
117 117 cs_data['message'], repo.repo_name)
118 118 cs_data['issues'] = issues_data
119 119 cs_data['message_html'] = urlify_commit_message(
120 120 cs_data['message'], repo.repo_name)
121 121 cs_data['message_html_title'] = chop_at_smart(
122 122 cs_data['message'], '\n', suffix_if_chopped='...')
123 123 commits.append(cs_data)
124 124
125 125 needed_commits.remove(commit_id)
126 126
127 127 except Exception:
128 128 log.exception('Failed to extract commits data')
129 129 # we don't send any commits when crash happens, only full list
130 130 # matters we short circuit then.
131 131 return []
132 132
133 133 # we failed to remove all needed_commits from all repositories
134 134 if needed_commits:
135 135 raise ValueError(f'Unexpectedly not found {needed_commits} in all available repos {repos}')
136 136
137 137 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
138 138 if missing_commits:
139 139 log.error('Inconsistent repository state. '
140 140 'Missing commits: %s', ', '.join(missing_commits))
141 141
142 142 return commits
143 143
144 144
145 145 def _issues_as_dict(commits):
146 146 """ Helper function to serialize issues from commits """
147 147 issues = {}
148 148 for commit in commits:
149 149 for issue in commit['issues']:
150 150 issues[issue['id']] = issue
151 151 return issues
152 152
153 153
154 154 class RepoEvent(RhodeCodeIntegrationEvent):
155 155 """
156 156 Base class for events acting on a repository.
157
158 :param repo: a :class:`Repository` instance
159 157 """
160 158
161 159 def __init__(self, repo):
160 """
161 :param repo: a :class:`Repository` instance
162 """
162 163 super().__init__()
163 164 self.repo = repo
164 165
165 166 def as_dict(self):
166 167 from rhodecode.model.repo import RepoModel
167 168 data = super().as_dict()
168 169
169 170 extra_fields = collections.OrderedDict()
170 171 for field in self.repo.extra_fields:
171 172 extra_fields[field.field_key] = field.field_value
172 173
173 174 data.update({
174 175 'repo': {
175 176 'repo_id': self.repo.repo_id,
176 177 'repo_name': self.repo.repo_name,
177 178 'repo_type': self.repo.repo_type,
178 179 'url': RepoModel().get_url(
179 180 self.repo, request=self.request),
180 181 'permalink_url': RepoModel().get_url(
181 182 self.repo, request=self.request, permalink=True),
182 183 'extra_fields': extra_fields
183 184 }
184 185 })
185 186 return data
186 187
187 188
188 189 class RepoCommitCommentEvent(RepoEvent):
189 190 """
190 191 An instance of this class is emitted as an :term:`event` after a comment is made
191 192 on repository commit.
192 193 """
193 194
194 195 name = 'repo-commit-comment'
195 196 display_name = lazy_ugettext('repository commit comment')
196 197 description = lazy_ugettext('Event triggered after a comment was made '
197 198 'on commit inside a repository')
198 199
199 200 def __init__(self, repo, commit, comment):
200 201 super().__init__(repo)
201 202 self.commit = commit
202 203 self.comment = comment
203 204
204 205 def as_dict(self):
205 206 data = super().as_dict()
206 207 data['commit'] = {
207 208 'commit_id': self.commit.raw_id,
208 209 'commit_message': self.commit.message,
209 210 'commit_branch': self.commit.branch,
210 211 }
211 212
212 213 data['comment'] = {
213 214 'comment_id': self.comment.comment_id,
214 215 'comment_text': self.comment.text,
215 216 'comment_type': self.comment.comment_type,
216 217 'comment_f_path': self.comment.f_path,
217 218 'comment_line_no': self.comment.line_no,
218 219 'comment_version': self.comment.last_version,
219 220 }
220 221 return data
221 222
222 223
223 224 class RepoCommitCommentEditEvent(RepoEvent):
224 225 """
225 226 An instance of this class is emitted as an :term:`event` after a comment is edited
226 227 on repository commit.
227 228 """
228 229
229 230 name = 'repo-commit-edit-comment'
230 231 display_name = lazy_ugettext('repository commit edit comment')
231 232 description = lazy_ugettext('Event triggered after a comment was edited '
232 233 'on commit inside a repository')
233 234
234 235 def __init__(self, repo, commit, comment):
235 236 super().__init__(repo)
236 237 self.commit = commit
237 238 self.comment = comment
238 239
239 240 def as_dict(self):
240 241 data = super().as_dict()
241 242 data['commit'] = {
242 243 'commit_id': self.commit.raw_id,
243 244 'commit_message': self.commit.message,
244 245 'commit_branch': self.commit.branch,
245 246 }
246 247
247 248 data['comment'] = {
248 249 'comment_id': self.comment.comment_id,
249 250 'comment_text': self.comment.text,
250 251 'comment_type': self.comment.comment_type,
251 252 'comment_f_path': self.comment.f_path,
252 253 'comment_line_no': self.comment.line_no,
253 254 'comment_version': self.comment.last_version,
254 255 }
255 256 return data
256 257
257 258
258 259 class RepoPreCreateEvent(RepoEvent):
259 260 """
260 261 An instance of this class is emitted as an :term:`event` before a repo is
261 262 created.
262 263 """
263 264 name = 'repo-pre-create'
264 265 display_name = lazy_ugettext('repository pre create')
265 266 description = lazy_ugettext('Event triggered before repository is created')
266 267
267 268
268 269 class RepoCreateEvent(RepoEvent):
269 270 """
270 271 An instance of this class is emitted as an :term:`event` whenever a repo is
271 272 created.
272 273 """
273 274 name = 'repo-create'
274 275 display_name = lazy_ugettext('repository created')
275 276 description = lazy_ugettext('Event triggered after repository was created')
276 277
277 278
278 279 class RepoPreDeleteEvent(RepoEvent):
279 280 """
280 281 An instance of this class is emitted as an :term:`event` whenever a repo is
281 282 created.
282 283 """
283 284 name = 'repo-pre-delete'
284 285 display_name = lazy_ugettext('repository pre delete')
285 286 description = lazy_ugettext('Event triggered before a repository is deleted')
286 287
287 288
288 289 class RepoDeleteEvent(RepoEvent):
289 290 """
290 291 An instance of this class is emitted as an :term:`event` whenever a repo is
291 292 created.
292 293 """
293 294 name = 'repo-delete'
294 295 display_name = lazy_ugettext('repository deleted')
295 296 description = lazy_ugettext('Event triggered after repository was deleted')
296 297
297 298
298 299 class RepoVCSEvent(RepoEvent):
299 300 """
300 301 Base class for events triggered by the VCS
301 302 """
303 name = ''
304 display_name = 'generic_vcs_event'
305
302 306 def __init__(self, repo_name, extras):
303 307 self.repo = Repository.get_by_repo_name(repo_name)
304 308 if not self.repo:
305 309 raise Exception(f'repo by this name {repo_name} does not exist')
306 310 self.extras = extras
307 311 super().__init__(self.repo)
308 312
309 313 @property
310 314 def actor(self):
311 315 if self.extras.get('username'):
312 316 return User.get_by_username(self.extras['username'])
313 317
314 318 @property
315 319 def actor_ip(self):
316 320 if self.extras.get('ip'):
317 321 return self.extras['ip']
318 322
319 323 @property
320 324 def server_url(self):
321 325 if self.extras.get('server_url'):
322 326 return self.extras['server_url']
323 327
324 328 @property
325 329 def request(self):
326 330 return self.extras.get('request') or self.get_request()
327 331
328 332
329 333 class RepoPrePullEvent(RepoVCSEvent):
330 334 """
331 335 An instance of this class is emitted as an :term:`event` before commits
332 336 are pulled from a repo.
333 337 """
334 338 name = 'repo-pre-pull'
335 339 display_name = lazy_ugettext('repository pre pull')
336 340 description = lazy_ugettext('Event triggered before repository code is pulled')
337 341
338 342
339 343 class RepoPullEvent(RepoVCSEvent):
340 344 """
341 345 An instance of this class is emitted as an :term:`event` after commits
342 346 are pulled from a repo.
343 347 """
344 348 name = 'repo-pull'
345 349 display_name = lazy_ugettext('repository pull')
346 350 description = lazy_ugettext('Event triggered after repository code was pulled')
347 351
348 352
349 353 class RepoPrePushEvent(RepoVCSEvent):
350 354 """
351 355 An instance of this class is emitted as an :term:`event` before commits
352 356 are pushed to a repo.
353 357 """
354 358 name = 'repo-pre-push'
355 359 display_name = lazy_ugettext('repository pre push')
356 360 description = lazy_ugettext('Event triggered before the code is '
357 361 'pushed to a repository')
358 362
359 363
360 364 class RepoPushEvent(RepoVCSEvent):
361 365 """
362 366 An instance of this class is emitted as an :term:`event` after commits
363 367 are pushed to a repo.
364 368
365 369 :param extras: (optional) dict of data from proxied VCS actions
366 370 """
367 371 name = 'repo-push'
368 372 display_name = lazy_ugettext('repository push')
369 373 description = lazy_ugettext('Event triggered after the code was '
370 374 'pushed to a repository')
371 375
372 376 def __init__(self, repo_name, pushed_commit_ids, extras):
373 377 super().__init__(repo_name, extras)
374 378 self.pushed_commit_ids = pushed_commit_ids
375 379 self.new_refs = extras.new_refs
376 380
377 381 def as_dict(self):
378 382 data = super().as_dict()
379 383
380 384 def branch_url(branch_name):
381 385 return '{}/changelog?branch={}'.format(
382 386 data['repo']['url'], branch_name)
383 387
384 388 def tag_url(tag_name):
385 389 return '{}/files/{}/'.format(
386 390 data['repo']['url'], tag_name)
387 391
388 392 commits = _commits_as_dict(
389 393 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
390 394
391 395 last_branch = None
392 396 for commit in reversed(commits):
393 397 commit['branch'] = commit['branch'] or last_branch
394 398 last_branch = commit['branch']
395 399 issues = _issues_as_dict(commits)
396 400
397 401 branches = set()
398 402 tags = set()
399 403 for commit in commits:
400 404 if commit['refs']['tags']:
401 405 for tag in commit['refs']['tags']:
402 406 tags.add(tag)
403 407 if commit['branch']:
404 408 branches.add(commit['branch'])
405 409
406 410 # maybe we have branches in new_refs ?
407 411 try:
408 412 branches = branches.union(set(self.new_refs['branches']))
409 413 except Exception:
410 414 pass
411 415
412 416 branches = [
413 417 {
414 418 'name': branch,
415 419 'url': branch_url(branch)
416 420 }
417 421 for branch in branches
418 422 ]
419 423
420 424 # maybe we have branches in new_refs ?
421 425 try:
422 426 tags = tags.union(set(self.new_refs['tags']))
423 427 except Exception:
424 428 pass
425 429
426 430 tags = [
427 431 {
428 432 'name': tag,
429 433 'url': tag_url(tag)
430 434 }
431 435 for tag in tags
432 436 ]
433 437
434 438 data['push'] = {
435 439 'commits': commits,
436 440 'issues': issues,
437 441 'branches': branches,
438 442 'tags': tags,
439 443 }
440 444 return data
@@ -1,93 +1,93 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import socket
20 20 import logging
21 21
22 22 import rhodecode
23 23 from zope.cachedescriptors.property import Lazy as LazyProperty
24 24 from rhodecode.lib.celerylib.loader import (
25 25 celery_app, RequestContextTask, get_logger)
26 26 from rhodecode.lib.statsd_client import StatsdClient
27 27
28 28 async_task = celery_app.task
29 29
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 class ResultWrapper(object):
35 35 def __init__(self, task):
36 36 self.task = task
37 37
38 38 @LazyProperty
39 39 def result(self):
40 40 return self.task
41 41
42 42
43 43 def run_task(task, *args, **kwargs):
44 44 import celery
45 45 log.debug('Got task `%s` for execution, celery mode enabled:%s', task, rhodecode.CELERY_ENABLED)
46 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 49 exec_mode = 'sync'
50 50 allow_async = True
51 51
52 52 # if we're already in a celery task, don't allow async execution again
53 53 # e.g task within task
54 54 in_task = celery.current_task
55 55 if in_task:
56 56 log.debug('This task in in context of another task: %s, not allowing another async execution', in_task)
57 57 allow_async = False
58 58 if kwargs.pop('allow_subtask', False):
59 59 log.debug('Forced async by allow_async=True flag')
60 60 allow_async = True
61 61
62 62 t = None
63 63 if rhodecode.CELERY_ENABLED and allow_async:
64 64
65 65 try:
66 66 t = task.apply_async(args=args, kwargs=kwargs)
67 67 log.debug('executing task %s:%s in async mode', t.task_id, task)
68 68 except socket.error as e:
69 69 if isinstance(e, IOError) and e.errno == 111:
70 70 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
71 71 else:
72 72 log.exception("Exception while connecting to celeryd.")
73 73 except KeyError as e:
74 74 log.error('Unable to connect to celeryd `%s`. Sync execution', e)
75 75 except Exception as e:
76 76 log.exception(
77 77 "Exception while trying to run task asynchronous. "
78 78 "Fallback to sync execution.")
79 79
80 80 else:
81 81 log.debug('executing task %s:%s in sync mode', 'TASK', task)
82 82 statsd = StatsdClient.statsd
83 83 if statsd:
84 84 task_repr = getattr(task, 'name', task)
85 85 statsd.incr('rhodecode_celery_task_total', tags=[
86 86 f'task:{task_repr}',
87 87 'mode:sync'
88 88 ])
89 89
90 90 # we got async task, return it after statsd call
91 91 if t:
92 92 return t
93 93 return ResultWrapper(task(*args, **kwargs))
@@ -1,173 +1,181 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import sqlalchemy
21 21 from sqlalchemy import UnicodeText
22 from sqlalchemy.ext.mutable import Mutable, \
23 MutableList as MutationList, \
24 MutableDict as MutationDict
22 from sqlalchemy.ext.mutable import Mutable, MutableList, MutableDict
25 23
26 24 from rhodecode.lib import ext_json
27 25
28 26
29 27 class JsonRaw(str):
30 28 """
31 29 Allows interacting with a JSON types field using a raw string.
32 30
33 For example::
31 For example:
34 32 db_instance = JsonTable()
35 33 db_instance.enabled = True
36 34 db_instance.json_data = JsonRaw('{"a": 4}')
37 35
38 36 This will bypass serialization/checks, and allow storing
39 37 raw values
40 38 """
41 39 pass
42 40
43 41
44 42 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
45 43 """
46 44 Represents an immutable structure as a json-encoded string.
47 45
48 46 If default is, for example, a dict, then a NULL value in the
49 47 database will be exposed as an empty dict.
50 48 """
51 49
52 50 impl = UnicodeText
53 51 safe = True
54 52 enforce_str = True
55 53
56 54 def __init__(self, *args, **kwargs):
57 55 self.default = kwargs.pop('default', None)
58 56 self.safe = kwargs.pop('safe_json', self.safe)
59 57 self.enforce_str = kwargs.pop('enforce_str', self.enforce_str)
60 58 self.dialect_map = kwargs.pop('dialect_map', {})
61 59 super(JSONEncodedObj, self).__init__(*args, **kwargs)
62 60
63 61 def load_dialect_impl(self, dialect):
64 62 if dialect.name in self.dialect_map:
65 63 return dialect.type_descriptor(self.dialect_map[dialect.name])
66 64 return dialect.type_descriptor(self.impl)
67 65
68 66 def process_bind_param(self, value, dialect):
69 67 if isinstance(value, JsonRaw):
70 68 value = value
71 69 elif value is not None:
72 70 if self.enforce_str:
73 71 value = ext_json.str_json(value)
74 72 else:
75 73 value = ext_json.json.dumps(value)
76 74 return value
77 75
78 76 def process_result_value(self, value, dialect):
79 77 if self.default is not None and (not value or value == '""'):
80 78 return self.default()
81 79
82 80 if value is not None:
83 81 try:
84 82 value = ext_json.json.loads(value)
85 83 except Exception:
86 84 if self.safe and self.default is not None:
87 85 return self.default()
88 86 else:
89 87 raise
90 88 return value
91 89
92 90
93 91 class MutationObj(Mutable):
94 92
95 93 @classmethod
96 94 def coerce(cls, key, value):
97 95 if isinstance(value, dict) and not isinstance(value, MutationDict):
98 96 return MutationDict.coerce(key, value)
99 97 if isinstance(value, list) and not isinstance(value, MutationList):
100 98 return MutationList.coerce(key, value)
101 99 return value
102 100
103 def de_coerce(self):
101 def de_coerce(self) -> "MutationObj":
104 102 return self
105 103
106 104 @classmethod
107 105 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
108 106 key = attribute.key
109 107 if parent_cls is not attribute.class_:
110 108 return
111 109
112 110 # rely on "propagate" here
113 111 parent_cls = attribute.class_
114 112
115 113 def load(state, *args):
116 114 val = state.dict.get(key, None)
117 115 if coerce:
118 116 val = cls.coerce(key, val)
119 117 state.dict[key] = val
120 118 if isinstance(val, cls):
121 119 val._parents[state.obj()] = key
122 120
123 121 def set(target, value, oldvalue, initiator):
124 122 if not isinstance(value, cls):
125 123 value = cls.coerce(key, value)
126 124 if isinstance(value, cls):
127 125 value._parents[target.obj()] = key
128 126 if isinstance(oldvalue, cls):
129 127 oldvalue._parents.pop(target.obj(), None)
130 128 return value
131 129
132 130 def pickle(state, state_dict):
133 131 val = state.dict.get(key, None)
134 132 if isinstance(val, cls):
135 133 if 'ext.mutable.values' not in state_dict:
136 134 state_dict['ext.mutable.values'] = []
137 135 state_dict['ext.mutable.values'].append(val)
138 136
139 137 def unpickle(state, state_dict):
140 138 if 'ext.mutable.values' in state_dict:
141 139 for val in state_dict['ext.mutable.values']:
142 140 val._parents[state.obj()] = key
143 141
144 142 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
145 143 propagate=True)
146 144 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
147 145 propagate=True)
148 146 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
149 147 propagate=True)
150 148 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
151 149 propagate=True)
152 150 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
153 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 164 def JsonType(impl=None, **kwargs):
157 165 """
158 166 Helper for using a mutation obj, it allows to use .with_variant easily.
159 167 example::
160 168
161 169 settings = Column('settings_json',
162 170 MutationObj.as_mutable(
163 171 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
164 172 """
165 173
166 174 if impl == 'list':
167 175 return JSONEncodedObj(default=list, **kwargs)
168 176 elif impl == 'dict':
169 177 return JSONEncodedObj(default=dict, **kwargs)
170 178 else:
171 179 return JSONEncodedObj(**kwargs)
172 180
173 181
1 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 1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import re
20 20 import logging
21 21
22 22 import ipaddress
23 23 import colander
24 24
25 25 from rhodecode.translation import _
26 26 from rhodecode.lib.utils2 import glob2re
27 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 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 def ip_addr_validator(node, value):
34 34 try:
35 35 # this raises an ValueError if address is not IpV4 or IpV6
36 36 ipaddress.ip_network(safe_str(value), strict=False)
37 37 except ValueError:
38 38 msg = _('Please enter a valid IPv4 or IpV6 address')
39 39 raise colander.Invalid(node, msg)
40 40
41 41
42 42 class IpAddrValidator(object):
43 43 def __init__(self, strict=True):
44 44 self.strict = strict
45 45
46 46 def __call__(self, node, value):
47 47 try:
48 48 # this raises an ValueError if address is not IpV4 or IpV6
49 49 ipaddress.ip_network(safe_str(value), strict=self.strict)
50 50 except ValueError:
51 51 msg = _('Please enter a valid IPv4 or IpV6 address')
52 52 raise colander.Invalid(node, msg)
53 53
54 54
55 55 def glob_validator(node, value):
56 56 try:
57 57 re.compile('^' + glob2re(value) + '$')
58 58 except Exception:
59 59 msg = _('Invalid glob pattern')
60 60 raise colander.Invalid(node, msg)
61 61
62 62
63 63 def valid_name_validator(node, value):
64 64 from rhodecode.model.validation_schema import types
65 65 if value is types.RootLocation:
66 66 return
67 67
68 68 msg = _('Name must start with a letter or number. Got `{}`').format(value)
69 69 if not re.match(r'^[a-zA-z0-9]{1,}', value):
70 70 raise colander.Invalid(node, msg)
71 71
72 72
73 73 class InvalidCloneUrl(Exception):
74 74 allowed_prefixes = ()
75 75
76 76
77 77 def url_validator(url, repo_type, config):
78 78 from rhodecode.lib.vcs.backends.hg import MercurialRepository
79 79 from rhodecode.lib.vcs.backends.git import GitRepository
80 80 from rhodecode.lib.vcs.backends.svn import SubversionRepository
81 81
82 82 if repo_type == 'hg':
83 83 allowed_prefixes = ('http', 'svn+http', 'git+http')
84 84
85 85 if 'http' in url[:4]:
86 86 # initially check if it's at least the proper URL
87 87 # or does it pass basic auth
88 88
89 89 return MercurialRepository.check_url(url, config)
90 90 elif 'svn+http' in url[:8]: # svn->hg import
91 91 SubversionRepository.check_url(url, config)
92 92 elif 'git+http' in url[:8]: # git->hg import
93 93 raise NotImplementedError()
94 94 else:
95 95 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
96 96 'Allowed url must start with one of %s'
97 97 % (url, ','.join(allowed_prefixes)))
98 98 exc.allowed_prefixes = allowed_prefixes
99 99 raise exc
100 100
101 101 elif repo_type == 'git':
102 102 allowed_prefixes = ('http', 'svn+http', 'hg+http')
103 103 if 'http' in url[:4]:
104 104 # initially check if it's at least the proper URL
105 105 # or does it pass basic auth
106 106 return GitRepository.check_url(url, config)
107 107 elif 'svn+http' in url[:8]: # svn->git import
108 108 raise NotImplementedError()
109 109 elif 'hg+http' in url[:8]: # hg->git import
110 110 raise NotImplementedError()
111 111 else:
112 112 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
113 113 'Allowed url must start with one of %s'
114 114 % (url, ','.join(allowed_prefixes)))
115 115 exc.allowed_prefixes = allowed_prefixes
116 116 raise exc
117 117 elif repo_type == 'svn':
118 118 # no validation for SVN yet
119 119 return
120 120
121 121 raise InvalidCloneUrl(f'Invalid repo type specified: `{repo_type}`')
122 122
123 123
124 124 class CloneUriValidator(object):
125 125 def __init__(self, repo_type):
126 126 self.repo_type = repo_type
127 127
128 128 def __call__(self, node, value):
129 129
130 130 from rhodecode.lib.utils import make_db_config
131 131 try:
132 132 config = make_db_config(clear_session=False)
133 133 url_validator(value, self.repo_type, config)
134 134 except InvalidCloneUrl as e:
135 135 log.warning(e)
136 136 raise colander.Invalid(node, str(e))
137 137 except Exception as e:
138 138 log.exception('Url validation failed')
139 139 reason = repr(e)
140 140 reason = reason.replace('<', '&lt;').replace('>', '&gt;')
141 141 msg = _('invalid clone url or credentials for {repo_type} repository. Reason: {reason}')\
142 142 .format(reason=reason, repo_type=self.repo_type)
143 143 raise colander.Invalid(node, msg)
144 144
145 145
146 146 def json_validator(node, value):
147 147 try:
148 148 json.loads(value)
149 149 except (Exception,):
150 150 msg = _('Please enter a valid json object')
151 151 raise colander.Invalid(node, msg)
152 152
153 153
154 154 def json_validator_with_exc(node, value):
155
155 156 try:
156 157 json.loads(value)
157 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 160 raise colander.Invalid(node, msg)
@@ -1,412 +1,412 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('admin_artifacts', '/_admin/artifacts', []);
16 16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
17 17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
18 18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
19 19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
20 20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
21 21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
22 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 25 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
24 26 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
25 27 pyroutes.register('admin_home', '/_admin', []);
26 28 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
27 29 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
28 30 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
29 31 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
30 32 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
31 33 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
32 34 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
33 35 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
34 36 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
35 37 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
36 38 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
37 39 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
38 40 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
39 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 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 45 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
44 46 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
45 47 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
46 48 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
47 49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
48 50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
49 51 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
50 52 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
51 53 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
52 54 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
53 55 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
54 56 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
55 57 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
56 58 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
57 59 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
58 60 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
59 61 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
60 62 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
61 63 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
62 64 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
63 65 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
64 66 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
65 67 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
66 68 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
67 69 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
68 70 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
69 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
70 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
71 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
72 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
73 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
74 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
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']);
71 pyroutes.register('admin_settings_scheduler_create', '/_admin/scheduler/create', []);
72 pyroutes.register('admin_settings_scheduler_delete', '/_admin/scheduler/%(schedule_id)s/delete', ['schedule_id']);
73 pyroutes.register('admin_settings_scheduler_edit', '/_admin/scheduler/%(schedule_id)s', ['schedule_id']);
74 pyroutes.register('admin_settings_scheduler_execute', '/_admin/scheduler/%(schedule_id)s/execute', ['schedule_id']);
75 pyroutes.register('admin_settings_scheduler_new', '/_admin/scheduler/new', []);
76 pyroutes.register('admin_settings_scheduler_update', '/_admin/scheduler/%(schedule_id)s/update', ['schedule_id']);
77 77 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
78 78 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
79 79 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
80 80 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
81 81 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
82 82 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
83 83 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
84 84 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
85 85 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
86 86 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
87 87 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
88 88 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
89 89 pyroutes.register('apiv2', '/_admin/api', []);
90 90 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
91 91 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
92 92 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
93 93 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
94 94 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
95 95 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
96 96 pyroutes.register('channelstream_proxy', '/_channelstream', []);
97 97 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
98 98 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
99 99 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
100 100 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
101 101 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
102 102 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
103 103 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
104 104 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
105 105 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
106 106 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
107 107 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
108 108 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
109 109 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
110 110 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
111 111 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
112 112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
113 113 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
114 114 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
115 115 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
116 116 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
117 117 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
118 118 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
119 119 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
120 120 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
121 121 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
122 122 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
123 123 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
124 124 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
125 125 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
126 126 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
127 127 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
128 128 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
129 129 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
130 130 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
131 131 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
132 132 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
133 133 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
134 134 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
135 135 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
136 136 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
137 137 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
138 138 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
139 139 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
140 140 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
141 141 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
142 142 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
143 143 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
144 144 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
145 145 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
146 146 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
147 147 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
148 148 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
149 149 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
150 150 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
151 151 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
152 152 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
153 153 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
154 154 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
155 155 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
156 156 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
157 157 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
158 158 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
159 159 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
160 160 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
161 161 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
162 162 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
163 163 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
164 164 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
165 165 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
166 166 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
167 167 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
168 168 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
169 169 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
170 170 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
171 171 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
172 172 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
173 173 pyroutes.register('favicon', '/favicon.ico', []);
174 174 pyroutes.register('file_preview', '/_file_preview', []);
175 175 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
176 176 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
177 177 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
178 178 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
179 179 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
180 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 181 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
182 182 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
183 183 pyroutes.register('gists_create', '/_admin/gists/create', []);
184 184 pyroutes.register('gists_new', '/_admin/gists/new', []);
185 185 pyroutes.register('gists_show', '/_admin/gists', []);
186 186 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
187 187 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
188 188 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
189 189 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
190 190 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
191 191 pyroutes.register('goto_switcher_data', '/_goto_data', []);
192 192 pyroutes.register('home', '/', []);
193 193 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
194 194 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
195 195 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
196 196 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
197 197 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
198 198 pyroutes.register('journal', '/_admin/journal', []);
199 199 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
200 200 pyroutes.register('journal_public', '/_admin/public_journal', []);
201 201 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
202 202 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
203 203 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
204 204 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
205 205 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
206 206 pyroutes.register('login', '/_admin/login', []);
207 207 pyroutes.register('logout', '/_admin/logout', []);
208 208 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
209 209 pyroutes.register('main_page_repos_data', '/_home_repos', []);
210 210 pyroutes.register('markup_preview', '/_markup_preview', []);
211 211 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
212 212 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
213 213 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
214 214 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
215 215 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
216 216 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
217 217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
218 218 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
219 219 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
220 220 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
221 221 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
222 222 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
223 223 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
224 224 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
225 225 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
226 226 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
227 227 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
228 228 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
229 229 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
230 230 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
231 231 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
232 232 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
233 233 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
234 234 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
235 235 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
236 236 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
237 237 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
238 238 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
239 239 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
240 240 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
241 241 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
242 242 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
243 243 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
244 244 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
245 245 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
246 246 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
247 247 pyroutes.register('ops_healthcheck', '/_admin/ops/status', []);
248 248 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
249 249 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
250 250 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
251 251 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
252 252 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
253 253 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
254 254 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
255 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 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 257 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
258 258 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
259 259 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
260 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 261 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
262 262 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
263 263 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
264 264 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
265 265 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
266 266 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
267 267 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
268 268 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
269 269 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
270 270 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
271 271 pyroutes.register('register', '/_admin/register', []);
272 272 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
273 273 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
274 274 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
275 275 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
276 276 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
277 277 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
278 278 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
279 279 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
280 280 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
281 281 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
282 282 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
283 283 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
284 284 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
285 285 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
286 286 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
287 287 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
288 288 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
289 289 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
290 290 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
291 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 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 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 294 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
295 295 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
296 296 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
297 297 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
298 298 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
299 299 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
300 300 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
301 301 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
302 302 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
303 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 304 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
305 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 306 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
307 307 pyroutes.register('repo_create', '/_admin/repos/create', []);
308 308 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
309 309 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
310 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 311 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
313 312 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
314 313 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 314 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 315 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 316 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 317 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
319 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 319 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
321 320 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
322 321 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 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 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 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 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 326 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
328 327 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
329 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 329 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
331 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 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 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 333 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
335 334 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
336 335 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
337 336 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
338 337 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
339 338 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
340 339 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
341 340 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
342 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 342 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
344 343 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
345 344 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
346 345 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
347 346 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
348 347 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
349 348 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
350 349 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
351 350 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
352 351 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
353 352 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
354 353 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
355 354 pyroutes.register('repo_list_data', '/_repos', []);
356 355 pyroutes.register('repo_new', '/_admin/repos/new', []);
357 356 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
358 357 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
359 358 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
360 359 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
361 360 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
362 361 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
363 362 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
364 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 365 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
366 366 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
367 367 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
368 368 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
369 369 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
370 370 pyroutes.register('repos', '/_admin/repos', []);
371 371 pyroutes.register('repos_data', '/_admin/repos_data', []);
372 372 pyroutes.register('reset_password', '/_admin/password_reset', []);
373 373 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
374 374 pyroutes.register('robots', '/robots.txt', []);
375 375 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
376 376 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
377 377 pyroutes.register('search', '/_admin/search', []);
378 378 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
379 379 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
380 380 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
381 381 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
382 382 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
383 383 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
384 384 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
385 385 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
386 386 pyroutes.register('upload_file', '/_file_store/upload', []);
387 387 pyroutes.register('user_autocomplete_data', '/_users', []);
388 388 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
389 389 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
390 390 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
391 391 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
392 392 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
393 393 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
394 394 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
395 395 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
396 396 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
397 397 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
398 398 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
399 399 pyroutes.register('user_groups', '/_admin/user_groups', []);
400 400 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
401 401 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
402 402 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
403 403 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
404 404 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
405 405 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
406 406 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
407 407 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
408 408 pyroutes.register('users', '/_admin/users', []);
409 409 pyroutes.register('users_create', '/_admin/users/create', []);
410 410 pyroutes.register('users_data', '/_admin/users_data', []);
411 411 pyroutes.register('users_new', '/_admin/users/new', []);
412 412 }
@@ -1,38 +1,41 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('Artifacts Admin')}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()"></%def>
11 11
12 12 <%def name="menu_bar_nav()">
13 13 ${self.menu_items(active='admin')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_subnav()">
17 17 ${self.admin_menu(active='artifacts')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21
22 22 <div class="box">
23 23
24 24 <div class="panel panel-default">
25 25 <div class="panel-heading">
26 26 <h3 class="panel-title">${_('Artifacts Administration.')}</h3>
27 27 </div>
28 28 <div class="panel-body">
29 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 34 </div>
32 35 </div>
33 36
34 37 </div>
35 38
36 39
37 40 </%def>
38 41
@@ -1,9 +1,37 b''
1 <div class="panel panel-default">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Admin Automation')}</h3>
1 <%inherit file="/base/base.mako"/>
2
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 32 </div>
5 <div class="panel-body">
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>
33
9 34 </div>
35
36
37 </%def>
@@ -1,147 +1,147 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 %if c.show_private:
5 5 ${_('Private Gists for user {}').format(c.rhodecode_user.username)}
6 6 %elif c.show_public:
7 7 ${_('Public Gists for user {}').format(c.rhodecode_user.username)}
8 8 %else:
9 9 ${_('Public Gists')}
10 10 %endif
11 11 %if c.rhodecode_name:
12 12 &middot; ${h.branding(c.rhodecode_name)}
13 13 %endif
14 14 </%def>
15 15
16 16 <%def name="breadcrumbs_links()"></%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='gists')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23
24 24 <div class="box">
25 25 <div class="title">
26 26
27 27 <ul class="button-links">
28 28 % if c.is_super_admin:
29 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 30 %endif
31 31 <li><a class="btn ${h.is_active('public', c.active)}" href="${h.route_path('gists_show')}">${_('All public')}</a></li>
32 32 %if c.rhodecode_user.username != h.DEFAULT_USER:
33 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 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 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 36 %endif
37 37 </ul>
38 38
39 39 % if c.rhodecode_user.username != h.DEFAULT_USER:
40 40 <div class="pull-right">
41 41 <a class="btn btn-primary" href="${h.route_path('gists_new')}" >
42 ${_(u'Create New Gist')}
42 ${_('Create New Gist')}
43 43 </a>
44 44 </div>
45 45 % endif
46 46
47 47 <div class="grid-quick-filter">
48 48 <ul class="grid-filter-box">
49 49 <li class="grid-filter-box-icon">
50 50 <i class="icon-search"></i>
51 51 </li>
52 52 <li class="grid-filter-box-input">
53 53 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
54 54 </li>
55 55 </ul>
56 56 </div>
57 57
58 58 </div>
59 59
60 60 <div class="main-content-full-width">
61 61 <div id="repos_list_wrap">
62 62 <table id="gist_list_table" class="display"></table>
63 63 </div>
64 64 </div>
65 65
66 66 </div>
67 67
68 68 <script type="text/javascript">
69 69 $(document).ready(function() {
70 70
71 71 var get_datatable_count = function(){
72 72 var api = $('#gist_list_table').dataTable().api();
73 73 $('#gists_count').text(api.page.info().recordsDisplay);
74 74 };
75 75
76 76
77 77 // custom filter that filters by access_id, description or author
78 78 $.fn.dataTable.ext.search.push(
79 79 function( settings, data, dataIndex ) {
80 80 var query = $('#q_filter').val();
81 81 var author = data[0].strip();
82 82 var access_id = data[2].strip();
83 83 var description = data[3].strip();
84 84
85 85 var query_str = (access_id + " " + author + " " + description).toLowerCase();
86 86
87 87 if(query_str.indexOf(query.toLowerCase()) !== -1){
88 88 return true;
89 89 }
90 90 return false;
91 91 }
92 92 );
93 93
94 94 // gists list
95 95 var gist_data = ${c.data|n};
96 96 $('#gist_list_table').DataTable({
97 97 data: gist_data,
98 98 dom: 'rtp',
99 99 pageLength: ${c.visual.dashboard_items},
100 100 order: [[ 4, "desc" ]],
101 101 columns: [
102 102 { data: {"_": "author",
103 103 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
104 104 { data: {"_": "type",
105 105 "sort": "type"}, title: "${_("Type")}", width: "100px", className: "td-gist-type" },
106 106 { data: {"_": "access_id",
107 107 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
108 108 { data: {"_": "description",
109 109 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
110 110 { data: {"_": "created_on",
111 111 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
112 112 { data: {"_": "expires",
113 113 "sort": "expires"}, title: "${_("Expires")}", width: "200px", className: "td-expire" }
114 114 ],
115 115 language: {
116 116 paginate: DEFAULT_GRID_PAGINATION,
117 117 emptyTable: _gettext("No gists available yet.")
118 118 },
119 119 "initComplete": function( settings, json ) {
120 120 timeagoActivate();
121 121 tooltipActivate();
122 122 get_datatable_count();
123 123 }
124 124 });
125 125
126 126 // update the counter when things change
127 127 $('#gist_list_table').on('draw.dt', function() {
128 128 timeagoActivate();
129 129 tooltipActivate();
130 130 get_datatable_count();
131 131 });
132 132
133 133 // filter, filter both grids
134 134 $('#q_filter').on( 'keyup', function () {
135 135 var repo_api = $('#gist_list_table').dataTable().api();
136 136 repo_api
137 137 .draw();
138 138 });
139 139
140 140 // refilter table if page load via back button
141 141 $("#q_filter").trigger('keyup');
142 142
143 143 });
144 144
145 145 </script>
146 146 </%def>
147 147
@@ -1,219 +1,219 b''
1 1 <%inherit file="base.mako"/>
2 2
3 3 <%def name="breadcrumbs_links()">
4 4 %if c.repo:
5 5 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
6 6 %elif c.repo_group:
7 7 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
8 8 &raquo;
9 9 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
10 10 &raquo;
11 11 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
12 12 %else:
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 14 &raquo;
15 15 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
16 16 %endif
17 17 %if c.current_IntegrationType:
18 18 &raquo;
19 19 %if c.repo:
20 20 ${h.link_to(_('Integrations'),
21 21 request.route_path(route_name='repo_integrations_home',
22 22 repo_name=c.repo.repo_name))}
23 23 %elif c.repo_group:
24 24 ${h.link_to(_('Integrations'),
25 25 request.route_path(route_name='repo_group_integrations_home',
26 26 repo_group_name=c.repo_group.group_name))}
27 27 %else:
28 28 ${h.link_to(_('Integrations'),
29 29 request.route_path(route_name='global_integrations_home'))}
30 30 %endif
31 31 &raquo;
32 32 ${c.current_IntegrationType.display_name}
33 33 %else:
34 34 &raquo;
35 35 ${_('Integrations')}
36 36 %endif
37 37 </%def>
38 38
39 39 <div class="panel panel-default">
40 40 <div class="panel-heading">
41 41 <h3 class="panel-title">
42 42 %if c.repo:
43 43 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 44 %elif c.repo_group:
45 45 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
46 46 %else:
47 47 ${_('Current Integrations')}
48 48 %endif
49 49 </h3>
50 50 </div>
51 51 <div class="panel-body">
52 52
53 53 <%
54 54 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
55 55
56 56 if c.repo:
57 57 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
58 58 elif c.repo_group:
59 59 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
60 60 else:
61 61 create_url = h.route_path('global_integrations_new')
62 62 %>
63 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 65 </p>
66 66
67 67 <table class="rctable integrations">
68 68 <thead>
69 69 <tr>
70 70 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
71 71 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
72 72 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
73 73 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
74 74 <th>${_('Actions')}</th>
75 75 <th></th>
76 76 </tr>
77 77 </thead>
78 78 <tbody>
79 79 %if not c.integrations_list:
80 80 <tr>
81 81 <td colspan="7">
82 82
83 83 %if c.repo:
84 84 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
85 85 %elif c.repo_group:
86 86 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
87 87 %else:
88 88 ${_('No {type} integrations exist yet.').format(type=integration_type)}
89 89 %endif
90 90
91 91 %if c.current_IntegrationType:
92 92 <%
93 93 if c.repo:
94 94 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
95 95 elif c.repo_group:
96 96 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
97 97 else:
98 98 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
99 99 %>
100 100 %endif
101 101
102 <a href="${create_url}">${_(u'Create one')}</a>
102 <a href="${create_url}">${_('Create one')}</a>
103 103 </td>
104 104 </tr>
105 105 %endif
106 106 %for IntegrationType, integration in c.integrations_list:
107 107 <tr id="integration_${integration.integration_id}">
108 108 <td class="td-enabled">
109 109 <div class="pull-left">
110 110 ${h.bool2icon(integration.enabled)}
111 111 </div>
112 112 </td>
113 113 <td class="td-description">
114 114 ${integration.name}
115 115 </td>
116 116 <td class="td-icon">
117 117 %if integration.integration_type in c.available_integrations:
118 118 <div class="integration-icon">
119 119 ${c.available_integrations[integration.integration_type].icon()|n}
120 120 </div>
121 121 %else:
122 122 ?
123 123 %endif
124 124 </td>
125 125 <td class="td-type">
126 126 ${integration.integration_type}
127 127 </td>
128 128 <td class="td-scope">
129 129 %if integration.repo:
130 130 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
131 131 ${_('repo')}:${integration.repo.repo_name}
132 132 </a>
133 133 %elif integration.repo_group:
134 134 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
135 135 ${_('repogroup')}:${integration.repo_group.group_name}
136 136 %if integration.child_repos_only:
137 137 ${_('child repos only')}
138 138 %else:
139 139 ${_('cascade to all')}
140 140 %endif
141 141 </a>
142 142 %else:
143 143 %if integration.child_repos_only:
144 144 ${_('top level repos only')}
145 145 %else:
146 146 ${_('global')}
147 147 %endif
148 148 </td>
149 149 %endif
150 150 <td class="td-action">
151 151 %if not IntegrationType:
152 152 ${_('unknown integration')}
153 153 %else:
154 154 <%
155 155 if c.repo:
156 156 edit_url = request.route_path('repo_integrations_edit',
157 157 repo_name=c.repo.repo_name,
158 158 integration=integration.integration_type,
159 159 integration_id=integration.integration_id)
160 160 elif c.repo_group:
161 161 edit_url = request.route_path('repo_group_integrations_edit',
162 162 repo_group_name=c.repo_group.group_name,
163 163 integration=integration.integration_type,
164 164 integration_id=integration.integration_id)
165 165 else:
166 166 edit_url = request.route_path('global_integrations_edit',
167 167 integration=integration.integration_type,
168 168 integration_id=integration.integration_id)
169 169 %>
170 170 <div class="grid_edit">
171 171 <a href="${edit_url}">${_('Edit')}</a>
172 172 </div>
173 173 <div class="grid_delete">
174 174 <a href="${edit_url}"
175 175 class="btn btn-link btn-danger delete_integration_entry"
176 176 data-desc="${integration.name}"
177 177 data-uid="${integration.integration_id}">
178 178 ${_('Delete')}
179 179 </a>
180 180 </div>
181 181 %endif
182 182 </td>
183 183 </tr>
184 184 %endfor
185 185 <tr id="last-row"></tr>
186 186 </tbody>
187 187 </table>
188 188 <div class="integrations-paginator">
189 189 <div class="pagination-wh pagination-left">
190 190 ${c.integrations_list.render()}
191 191 </div>
192 192 </div>
193 193 </div>
194 194 </div>
195 195 <script type="text/javascript">
196 196 var delete_integration = function(entry) {
197 197 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
198 198 var request = $.ajax({
199 199 type: "POST",
200 200 url: $(entry).attr('href'),
201 201 data: {
202 202 'delete': 'delete',
203 203 'csrf_token': CSRF_TOKEN
204 204 },
205 205 success: function(){
206 206 location.reload();
207 207 },
208 208 error: function(data, textStatus, errorThrown){
209 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 215 $('.delete_integration_entry').on('click', function(e){
216 216 e.preventDefault();
217 217 delete_integration(this);
218 218 });
219 219 </script> No newline at end of file
@@ -1,67 +1,67 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('Settings administration')}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()"></%def>
11 11
12 12 <%def name="menu_bar_nav()">
13 13 ${self.menu_items(active='admin')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_subnav()">
17 17 ${self.admin_menu()}
18 18 </%def>
19 19
20 20 <%def name="side_bar_nav()">
21 21
22 22 </%def>
23 23
24 24 <%def name="main_content()">
25 25 <h2>${_('Administration area')}</h2>
26 26
27 27 <table class="rctable">
28 28 <tr>
29 29 <td>${_('Repositories under administration')}</td>
30 30 <td class="delegated-admin-repos">${len(c.auth_user.repositories_admin)}</td>
31 31 <td>
32 32 % if c.can_create_repo:
33 33 <a href="${h.route_path('repo_new')}" class="">${_('Add Repository')}</a>
34 34 % endif
35 35 </td>
36 36 </tr>
37 37 <tr>
38 38 <td>${_('Repository groups under administration')}</td>
39 39 <td class="delegated-admin-repo-groups">${len(c.auth_user.repository_groups_admin)}</td>
40 40 <td>
41 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 43 % endif
44 44 </td>
45 45 </tr>
46 46 <tr>
47 47 <td>${_('User groups under administration')}</td>
48 48 <td class="delegated-admin-user-groups">${len(c.auth_user.user_groups_admin)}</td>
49 49 <td>
50 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 52 % endif
53 53 </td>
54 54 </tr>
55 55 </table>
56 56 </%def>
57 57
58 58 <%def name="main()">
59 59 <div class="box">
60 60
61 61 ##main
62 62 <div class="main-content-auto-width">
63 63 ${self.main_content()}
64 64 </div>
65 65 </div>
66 66
67 67 </%def> No newline at end of file
@@ -1,116 +1,116 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('Repository groups administration')}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()"></%def>
11 11
12 12 <%def name="menu_bar_nav()">
13 13 ${self.menu_items(active='admin')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_subnav()">
17 17 ${self.admin_menu(active='repository_groups')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22
23 23 <div class="title">
24 24 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 25 <span id="repo_group_count"></span>
26 26
27 27 <ul class="links">
28 28 %if c.can_create_repo_group:
29 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 31 </li>
32 32 %endif
33 33 </ul>
34 34 </div>
35 35 <div id="repos_list_wrap">
36 36 <table id="group_list_table" class="display"></table>
37 37 </div>
38 38 </div>
39 39
40 40 <script>
41 41 $(document).ready(function() {
42 42 var $repoGroupsListTable = $('#group_list_table');
43 43
44 44 // repo group list
45 45 $repoGroupsListTable.DataTable({
46 46 processing: true,
47 47 serverSide: true,
48 48 ajax: {
49 49 "url": "${h.route_path('repo_groups_data')}",
50 50 "dataSrc": function (json) {
51 51 var filteredCount = json.recordsFiltered;
52 52 var filteredInactiveCount = json.recordsFilteredInactive;
53 53 var totalInactive = json.recordsTotalInactive;
54 54 var total = json.recordsTotal;
55 55
56 56 var _text = _gettext(
57 57 "{0} of {1} repository groups").format(
58 58 filteredCount, total);
59 59
60 60 if (total === filteredCount) {
61 61 _text = _gettext("{0} repository groups").format(total);
62 62 }
63 63 $('#repo_group_count').text(_text);
64 64 return json.data;
65 65 },
66 66 },
67 67
68 68 dom: 'rtp',
69 69 pageLength: ${c.visual.admin_grid_items},
70 70 order: [[ 0, "asc" ]],
71 71 columns: [
72 72 { data: {"_": "name",
73 73 "sort": "name"}, title: "${_('Name')}", className: "td-componentname" },
74 74 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
75 75 { data: {"_": "desc",
76 76 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
77 77 { data: {"_": "last_change",
78 78 "sort": "last_change",
79 79 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
80 80 { data: {"_": "top_level_repos",
81 81 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
82 82 { data: {"_": "owner",
83 83 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
84 84 { data: {"_": "action",
85 85 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
86 86 ],
87 87 language: {
88 88 paginate: DEFAULT_GRID_PAGINATION,
89 89 sProcessing: _gettext('loading...'),
90 90 emptyTable: _gettext("No repository groups available yet.")
91 91 },
92 92 });
93 93
94 94 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
95 95 $repoGroupsListTable.css('opacity', 1);
96 96 });
97 97
98 98 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
99 99 $repoGroupsListTable.css('opacity', 0.3);
100 100 });
101 101
102 102 // filter
103 103 $('#q_filter').on('keyup',
104 104 $.debounce(250, function() {
105 105 $repoGroupsListTable.DataTable().search(
106 106 $('#q_filter').val()
107 107 ).draw();
108 108 })
109 109 );
110 110
111 111 });
112 112
113 113 </script>
114 114
115 115 </%def>
116 116
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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