##// END OF EJS Templates
routing: switched static redirection links to pyramid....
marcink -
r1679:c94a1c49 default
parent child Browse files
Show More
@@ -1,41 +1,45 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.config import routing_links
20 21
21 22
22 23 def includeme(config):
23 24
24 25 config.add_route(
25 26 name='user_autocomplete_data',
26 27 pattern='/_users')
27 28
28 29 config.add_route(
29 30 name='user_group_autocomplete_data',
30 31 pattern='/_user_groups')
31 32
32 33 config.add_route(
33 34 name='repo_list_data',
34 35 pattern='/_repos')
35 36
36 37 config.add_route(
37 38 name='goto_switcher_data',
38 39 pattern='/_goto_data')
39 40
41 # register our static links via redirection mechanismy
42 routing_links.connect_redirection_links(config)
43
40 44 # Scan module for configuration decorators.
41 45 config.scan()
@@ -1,1144 +1,1125 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 from rhodecode.config import routing_links
36
37 35 # prefix for non repository related links needs to be prefixed with `/`
38 36 ADMIN_PREFIX = '/_admin'
39 37 STATIC_FILE_PREFIX = '/_static'
40 38
41 39 # Default requirements for URL parts
42 40 URL_NAME_REQUIREMENTS = {
43 41 # group name can have a slash in them, but they must not end with a slash
44 42 'group_name': r'.*?[^/]',
45 43 'repo_group_name': r'.*?[^/]',
46 44 # repo names can have a slash in them, but they must not end with a slash
47 45 'repo_name': r'.*?[^/]',
48 46 # file path eats up everything at the end
49 47 'f_path': r'.*',
50 48 # reference types
51 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 51 }
54 52
55 53
56 54 def add_route_requirements(route_path, requirements):
57 55 """
58 56 Adds regex requirements to pyramid routes using a mapping dict
59 57
60 58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 59 '/{action}/{id:\d+}'
62 60
63 61 """
64 62 for key, regex in requirements.items():
65 63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 64 return route_path
67 65
68 66
69 67 class JSRoutesMapper(Mapper):
70 68 """
71 69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 70 """
73 71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 73 def __init__(self, *args, **kw):
76 74 super(JSRoutesMapper, self).__init__(*args, **kw)
77 75 self._jsroutes = []
78 76
79 77 def connect(self, *args, **kw):
80 78 """
81 79 Wrapper for connect to take an extra argument jsroute=True
82 80
83 81 :param jsroute: boolean, if True will add the route to the pyroutes list
84 82 """
85 83 if kw.pop('jsroute', False):
86 84 if not self._named_route_regex.match(args[0]):
87 85 raise Exception('only named routes can be added to pyroutes')
88 86 self._jsroutes.append(args[0])
89 87
90 88 super(JSRoutesMapper, self).connect(*args, **kw)
91 89
92 90 def _extract_route_information(self, route):
93 91 """
94 92 Convert a route into tuple(name, path, args), eg:
95 93 ('show_user', '/profile/%(username)s', ['username'])
96 94 """
97 95 routepath = route.routepath
98 96 def replace(matchobj):
99 97 if matchobj.group(1):
100 98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 99 else:
102 100 return "%%(%s)s" % matchobj.group(2)
103 101
104 102 routepath = self._argument_prog.sub(replace, routepath)
105 103 return (
106 104 route.name,
107 105 routepath,
108 106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 107 for arg in self._argument_prog.findall(route.routepath)]
110 108 )
111 109
112 110 def jsroutes(self):
113 111 """
114 112 Return a list of pyroutes.js compatible routes
115 113 """
116 114 for route_name in self._jsroutes:
117 115 yield self._extract_route_information(self._routenames[route_name])
118 116
119 117
120 118 def make_map(config):
121 119 """Create, configure and return the routes Mapper"""
122 120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 121 always_scan=config['debug'])
124 122 rmap.minimization = False
125 123 rmap.explicit = False
126 124
127 125 from rhodecode.lib.utils2 import str2bool
128 126 from rhodecode.model import repo, repo_group
129 127
130 128 def check_repo(environ, match_dict):
131 129 """
132 130 check for valid repository for proper 404 handling
133 131
134 132 :param environ:
135 133 :param match_dict:
136 134 """
137 135 repo_name = match_dict.get('repo_name')
138 136
139 137 if match_dict.get('f_path'):
140 138 # fix for multiple initial slashes that causes errors
141 139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 140 repo_model = repo.RepoModel()
143 141 by_name_match = repo_model.get_by_repo_name(repo_name)
144 142 # if we match quickly from database, short circuit the operation,
145 143 # and validate repo based on the type.
146 144 if by_name_match:
147 145 return True
148 146
149 147 by_id_match = repo_model.get_repo_by_id(repo_name)
150 148 if by_id_match:
151 149 repo_name = by_id_match.repo_name
152 150 match_dict['repo_name'] = repo_name
153 151 return True
154 152
155 153 return False
156 154
157 155 def check_group(environ, match_dict):
158 156 """
159 157 check for valid repository group path for proper 404 handling
160 158
161 159 :param environ:
162 160 :param match_dict:
163 161 """
164 162 repo_group_name = match_dict.get('group_name')
165 163 repo_group_model = repo_group.RepoGroupModel()
166 164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 165 if by_name_match:
168 166 return True
169 167
170 168 return False
171 169
172 170 def check_user_group(environ, match_dict):
173 171 """
174 172 check for valid user group for proper 404 handling
175 173
176 174 :param environ:
177 175 :param match_dict:
178 176 """
179 177 return True
180 178
181 179 def check_int(environ, match_dict):
182 180 return match_dict.get('id').isdigit()
183 181
184 182
185 183 #==========================================================================
186 184 # CUSTOM ROUTES HERE
187 185 #==========================================================================
188 186
189 187 # MAIN PAGE
190 188 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 189
192 # TODO: johbo: Static links, to be replaced by our redirection mechanism
193 rmap.connect('rst_help',
194 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
195 _static=True)
196 rmap.connect('markdown_help',
197 'http://daringfireball.net/projects/markdown/syntax',
198 _static=True)
199 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
200 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
201 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
202 # TODO: anderson - making this a static link since redirect won't play
203 # nice with POST requests
204 rmap.connect('enterprise_license_convert_from_old',
205 'https://rhodecode.com/u/license-upgrade',
206 _static=True)
207
208 routing_links.connect_redirection_links(rmap)
209
190 # ping and pylons error test
210 191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
211 192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
212 193
213 194 # ADMIN REPOSITORY ROUTES
214 195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
215 196 controller='admin/repos') as m:
216 197 m.connect('repos', '/repos',
217 198 action='create', conditions={'method': ['POST']})
218 199 m.connect('repos', '/repos',
219 200 action='index', conditions={'method': ['GET']})
220 201 m.connect('new_repo', '/create_repository', jsroute=True,
221 202 action='create_repository', conditions={'method': ['GET']})
222 203 m.connect('/repos/{repo_name}',
223 204 action='update', conditions={'method': ['PUT'],
224 205 'function': check_repo},
225 206 requirements=URL_NAME_REQUIREMENTS)
226 207 m.connect('delete_repo', '/repos/{repo_name}',
227 208 action='delete', conditions={'method': ['DELETE']},
228 209 requirements=URL_NAME_REQUIREMENTS)
229 210 m.connect('repo', '/repos/{repo_name}',
230 211 action='show', conditions={'method': ['GET'],
231 212 'function': check_repo},
232 213 requirements=URL_NAME_REQUIREMENTS)
233 214
234 215 # ADMIN REPOSITORY GROUPS ROUTES
235 216 with rmap.submapper(path_prefix=ADMIN_PREFIX,
236 217 controller='admin/repo_groups') as m:
237 218 m.connect('repo_groups', '/repo_groups',
238 219 action='create', conditions={'method': ['POST']})
239 220 m.connect('repo_groups', '/repo_groups',
240 221 action='index', conditions={'method': ['GET']})
241 222 m.connect('new_repo_group', '/repo_groups/new',
242 223 action='new', conditions={'method': ['GET']})
243 224 m.connect('update_repo_group', '/repo_groups/{group_name}',
244 225 action='update', conditions={'method': ['PUT'],
245 226 'function': check_group},
246 227 requirements=URL_NAME_REQUIREMENTS)
247 228
248 229 # EXTRAS REPO GROUP ROUTES
249 230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
250 231 action='edit',
251 232 conditions={'method': ['GET'], 'function': check_group},
252 233 requirements=URL_NAME_REQUIREMENTS)
253 234 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
254 235 action='edit',
255 236 conditions={'method': ['PUT'], 'function': check_group},
256 237 requirements=URL_NAME_REQUIREMENTS)
257 238
258 239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
259 240 action='edit_repo_group_advanced',
260 241 conditions={'method': ['GET'], 'function': check_group},
261 242 requirements=URL_NAME_REQUIREMENTS)
262 243 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
263 244 action='edit_repo_group_advanced',
264 245 conditions={'method': ['PUT'], 'function': check_group},
265 246 requirements=URL_NAME_REQUIREMENTS)
266 247
267 248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
268 249 action='edit_repo_group_perms',
269 250 conditions={'method': ['GET'], 'function': check_group},
270 251 requirements=URL_NAME_REQUIREMENTS)
271 252 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
272 253 action='update_perms',
273 254 conditions={'method': ['PUT'], 'function': check_group},
274 255 requirements=URL_NAME_REQUIREMENTS)
275 256
276 257 m.connect('delete_repo_group', '/repo_groups/{group_name}',
277 258 action='delete', conditions={'method': ['DELETE'],
278 259 'function': check_group},
279 260 requirements=URL_NAME_REQUIREMENTS)
280 261
281 262 # ADMIN USER ROUTES
282 263 with rmap.submapper(path_prefix=ADMIN_PREFIX,
283 264 controller='admin/users') as m:
284 265 m.connect('users', '/users',
285 266 action='create', conditions={'method': ['POST']})
286 267 m.connect('new_user', '/users/new',
287 268 action='new', conditions={'method': ['GET']})
288 269 m.connect('update_user', '/users/{user_id}',
289 270 action='update', conditions={'method': ['PUT']})
290 271 m.connect('delete_user', '/users/{user_id}',
291 272 action='delete', conditions={'method': ['DELETE']})
292 273 m.connect('edit_user', '/users/{user_id}/edit',
293 274 action='edit', conditions={'method': ['GET']}, jsroute=True)
294 275 m.connect('user', '/users/{user_id}',
295 276 action='show', conditions={'method': ['GET']})
296 277 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
297 278 action='reset_password', conditions={'method': ['POST']})
298 279 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
299 280 action='create_personal_repo_group', conditions={'method': ['POST']})
300 281
301 282 # EXTRAS USER ROUTES
302 283 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
303 284 action='edit_advanced', conditions={'method': ['GET']})
304 285 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
305 286 action='update_advanced', conditions={'method': ['PUT']})
306 287
307 288 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
308 289 action='edit_global_perms', conditions={'method': ['GET']})
309 290 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
310 291 action='update_global_perms', conditions={'method': ['PUT']})
311 292
312 293 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
313 294 action='edit_perms_summary', conditions={'method': ['GET']})
314 295
315 296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
316 297 action='edit_emails', conditions={'method': ['GET']})
317 298 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
318 299 action='add_email', conditions={'method': ['PUT']})
319 300 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
320 301 action='delete_email', conditions={'method': ['DELETE']})
321 302
322 303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
323 304 action='edit_ips', conditions={'method': ['GET']})
324 305 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
325 306 action='add_ip', conditions={'method': ['PUT']})
326 307 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
327 308 action='delete_ip', conditions={'method': ['DELETE']})
328 309
329 310 # ADMIN USER GROUPS REST ROUTES
330 311 with rmap.submapper(path_prefix=ADMIN_PREFIX,
331 312 controller='admin/user_groups') as m:
332 313 m.connect('users_groups', '/user_groups',
333 314 action='create', conditions={'method': ['POST']})
334 315 m.connect('users_groups', '/user_groups',
335 316 action='index', conditions={'method': ['GET']})
336 317 m.connect('new_users_group', '/user_groups/new',
337 318 action='new', conditions={'method': ['GET']})
338 319 m.connect('update_users_group', '/user_groups/{user_group_id}',
339 320 action='update', conditions={'method': ['PUT']})
340 321 m.connect('delete_users_group', '/user_groups/{user_group_id}',
341 322 action='delete', conditions={'method': ['DELETE']})
342 323 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
343 324 action='edit', conditions={'method': ['GET']},
344 325 function=check_user_group)
345 326
346 327 # EXTRAS USER GROUP ROUTES
347 328 m.connect('edit_user_group_global_perms',
348 329 '/user_groups/{user_group_id}/edit/global_permissions',
349 330 action='edit_global_perms', conditions={'method': ['GET']})
350 331 m.connect('edit_user_group_global_perms',
351 332 '/user_groups/{user_group_id}/edit/global_permissions',
352 333 action='update_global_perms', conditions={'method': ['PUT']})
353 334 m.connect('edit_user_group_perms_summary',
354 335 '/user_groups/{user_group_id}/edit/permissions_summary',
355 336 action='edit_perms_summary', conditions={'method': ['GET']})
356 337
357 338 m.connect('edit_user_group_perms',
358 339 '/user_groups/{user_group_id}/edit/permissions',
359 340 action='edit_perms', conditions={'method': ['GET']})
360 341 m.connect('edit_user_group_perms',
361 342 '/user_groups/{user_group_id}/edit/permissions',
362 343 action='update_perms', conditions={'method': ['PUT']})
363 344
364 345 m.connect('edit_user_group_advanced',
365 346 '/user_groups/{user_group_id}/edit/advanced',
366 347 action='edit_advanced', conditions={'method': ['GET']})
367 348
368 349 m.connect('edit_user_group_advanced_sync',
369 350 '/user_groups/{user_group_id}/edit/advanced/sync',
370 351 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
371 352
372 353 m.connect('edit_user_group_members',
373 354 '/user_groups/{user_group_id}/edit/members', jsroute=True,
374 355 action='user_group_members', conditions={'method': ['GET']})
375 356
376 357 # ADMIN PERMISSIONS ROUTES
377 358 with rmap.submapper(path_prefix=ADMIN_PREFIX,
378 359 controller='admin/permissions') as m:
379 360 m.connect('admin_permissions_application', '/permissions/application',
380 361 action='permission_application_update', conditions={'method': ['POST']})
381 362 m.connect('admin_permissions_application', '/permissions/application',
382 363 action='permission_application', conditions={'method': ['GET']})
383 364
384 365 m.connect('admin_permissions_global', '/permissions/global',
385 366 action='permission_global_update', conditions={'method': ['POST']})
386 367 m.connect('admin_permissions_global', '/permissions/global',
387 368 action='permission_global', conditions={'method': ['GET']})
388 369
389 370 m.connect('admin_permissions_object', '/permissions/object',
390 371 action='permission_objects_update', conditions={'method': ['POST']})
391 372 m.connect('admin_permissions_object', '/permissions/object',
392 373 action='permission_objects', conditions={'method': ['GET']})
393 374
394 375 m.connect('admin_permissions_ips', '/permissions/ips',
395 376 action='permission_ips', conditions={'method': ['POST']})
396 377 m.connect('admin_permissions_ips', '/permissions/ips',
397 378 action='permission_ips', conditions={'method': ['GET']})
398 379
399 380 m.connect('admin_permissions_overview', '/permissions/overview',
400 381 action='permission_perms', conditions={'method': ['GET']})
401 382
402 383 # ADMIN DEFAULTS REST ROUTES
403 384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
404 385 controller='admin/defaults') as m:
405 386 m.connect('admin_defaults_repositories', '/defaults/repositories',
406 387 action='update_repository_defaults', conditions={'method': ['POST']})
407 388 m.connect('admin_defaults_repositories', '/defaults/repositories',
408 389 action='index', conditions={'method': ['GET']})
409 390
410 391 # ADMIN DEBUG STYLE ROUTES
411 392 if str2bool(config.get('debug_style')):
412 393 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
413 394 controller='debug_style') as m:
414 395 m.connect('debug_style_home', '',
415 396 action='index', conditions={'method': ['GET']})
416 397 m.connect('debug_style_template', '/t/{t_path}',
417 398 action='template', conditions={'method': ['GET']})
418 399
419 400 # ADMIN SETTINGS ROUTES
420 401 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 402 controller='admin/settings') as m:
422 403
423 404 # default
424 405 m.connect('admin_settings', '/settings',
425 406 action='settings_global_update',
426 407 conditions={'method': ['POST']})
427 408 m.connect('admin_settings', '/settings',
428 409 action='settings_global', conditions={'method': ['GET']})
429 410
430 411 m.connect('admin_settings_vcs', '/settings/vcs',
431 412 action='settings_vcs_update',
432 413 conditions={'method': ['POST']})
433 414 m.connect('admin_settings_vcs', '/settings/vcs',
434 415 action='settings_vcs',
435 416 conditions={'method': ['GET']})
436 417 m.connect('admin_settings_vcs', '/settings/vcs',
437 418 action='delete_svn_pattern',
438 419 conditions={'method': ['DELETE']})
439 420
440 421 m.connect('admin_settings_mapping', '/settings/mapping',
441 422 action='settings_mapping_update',
442 423 conditions={'method': ['POST']})
443 424 m.connect('admin_settings_mapping', '/settings/mapping',
444 425 action='settings_mapping', conditions={'method': ['GET']})
445 426
446 427 m.connect('admin_settings_global', '/settings/global',
447 428 action='settings_global_update',
448 429 conditions={'method': ['POST']})
449 430 m.connect('admin_settings_global', '/settings/global',
450 431 action='settings_global', conditions={'method': ['GET']})
451 432
452 433 m.connect('admin_settings_visual', '/settings/visual',
453 434 action='settings_visual_update',
454 435 conditions={'method': ['POST']})
455 436 m.connect('admin_settings_visual', '/settings/visual',
456 437 action='settings_visual', conditions={'method': ['GET']})
457 438
458 439 m.connect('admin_settings_issuetracker',
459 440 '/settings/issue-tracker', action='settings_issuetracker',
460 441 conditions={'method': ['GET']})
461 442 m.connect('admin_settings_issuetracker_save',
462 443 '/settings/issue-tracker/save',
463 444 action='settings_issuetracker_save',
464 445 conditions={'method': ['POST']})
465 446 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
466 447 action='settings_issuetracker_test',
467 448 conditions={'method': ['POST']})
468 449 m.connect('admin_issuetracker_delete',
469 450 '/settings/issue-tracker/delete',
470 451 action='settings_issuetracker_delete',
471 452 conditions={'method': ['DELETE']})
472 453
473 454 m.connect('admin_settings_email', '/settings/email',
474 455 action='settings_email_update',
475 456 conditions={'method': ['POST']})
476 457 m.connect('admin_settings_email', '/settings/email',
477 458 action='settings_email', conditions={'method': ['GET']})
478 459
479 460 m.connect('admin_settings_hooks', '/settings/hooks',
480 461 action='settings_hooks_update',
481 462 conditions={'method': ['POST', 'DELETE']})
482 463 m.connect('admin_settings_hooks', '/settings/hooks',
483 464 action='settings_hooks', conditions={'method': ['GET']})
484 465
485 466 m.connect('admin_settings_search', '/settings/search',
486 467 action='settings_search', conditions={'method': ['GET']})
487 468
488 469 m.connect('admin_settings_supervisor', '/settings/supervisor',
489 470 action='settings_supervisor', conditions={'method': ['GET']})
490 471 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
491 472 action='settings_supervisor_log', conditions={'method': ['GET']})
492 473
493 474 m.connect('admin_settings_labs', '/settings/labs',
494 475 action='settings_labs_update',
495 476 conditions={'method': ['POST']})
496 477 m.connect('admin_settings_labs', '/settings/labs',
497 478 action='settings_labs', conditions={'method': ['GET']})
498 479
499 480 # ADMIN MY ACCOUNT
500 481 with rmap.submapper(path_prefix=ADMIN_PREFIX,
501 482 controller='admin/my_account') as m:
502 483
503 484 m.connect('my_account_edit', '/my_account/edit',
504 485 action='my_account_edit', conditions={'method': ['GET']})
505 486 m.connect('my_account', '/my_account/update',
506 487 action='my_account_update', conditions={'method': ['POST']})
507 488
508 489 # NOTE(marcink): this needs to be kept for password force flag to be
509 490 # handler, remove after migration to pyramid
510 491 m.connect('my_account_password', '/my_account/password',
511 492 action='my_account_password', conditions={'method': ['GET']})
512 493
513 494 m.connect('my_account_repos', '/my_account/repos',
514 495 action='my_account_repos', conditions={'method': ['GET']})
515 496
516 497 m.connect('my_account_watched', '/my_account/watched',
517 498 action='my_account_watched', conditions={'method': ['GET']})
518 499
519 500 m.connect('my_account_pullrequests', '/my_account/pull_requests',
520 501 action='my_account_pullrequests', conditions={'method': ['GET']})
521 502
522 503 m.connect('my_account_perms', '/my_account/perms',
523 504 action='my_account_perms', conditions={'method': ['GET']})
524 505
525 506 m.connect('my_account_emails', '/my_account/emails',
526 507 action='my_account_emails', conditions={'method': ['GET']})
527 508 m.connect('my_account_emails', '/my_account/emails',
528 509 action='my_account_emails_add', conditions={'method': ['POST']})
529 510 m.connect('my_account_emails', '/my_account/emails',
530 511 action='my_account_emails_delete', conditions={'method': ['DELETE']})
531 512
532 513 m.connect('my_account_notifications', '/my_account/notifications',
533 514 action='my_notifications',
534 515 conditions={'method': ['GET']})
535 516 m.connect('my_account_notifications_toggle_visibility',
536 517 '/my_account/toggle_visibility',
537 518 action='my_notifications_toggle_visibility',
538 519 conditions={'method': ['POST']})
539 520 m.connect('my_account_notifications_test_channelstream',
540 521 '/my_account/test_channelstream',
541 522 action='my_account_notifications_test_channelstream',
542 523 conditions={'method': ['POST']})
543 524
544 525 # NOTIFICATION REST ROUTES
545 526 with rmap.submapper(path_prefix=ADMIN_PREFIX,
546 527 controller='admin/notifications') as m:
547 528 m.connect('notifications', '/notifications',
548 529 action='index', conditions={'method': ['GET']})
549 530 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
550 531 action='mark_all_read', conditions={'method': ['POST']})
551 532 m.connect('/notifications/{notification_id}',
552 533 action='update', conditions={'method': ['PUT']})
553 534 m.connect('/notifications/{notification_id}',
554 535 action='delete', conditions={'method': ['DELETE']})
555 536 m.connect('notification', '/notifications/{notification_id}',
556 537 action='show', conditions={'method': ['GET']})
557 538
558 539 # ADMIN GIST
559 540 with rmap.submapper(path_prefix=ADMIN_PREFIX,
560 541 controller='admin/gists') as m:
561 542 m.connect('gists', '/gists',
562 543 action='create', conditions={'method': ['POST']})
563 544 m.connect('gists', '/gists', jsroute=True,
564 545 action='index', conditions={'method': ['GET']})
565 546 m.connect('new_gist', '/gists/new', jsroute=True,
566 547 action='new', conditions={'method': ['GET']})
567 548
568 549 m.connect('/gists/{gist_id}',
569 550 action='delete', conditions={'method': ['DELETE']})
570 551 m.connect('edit_gist', '/gists/{gist_id}/edit',
571 552 action='edit_form', conditions={'method': ['GET']})
572 553 m.connect('edit_gist', '/gists/{gist_id}/edit',
573 554 action='edit', conditions={'method': ['POST']})
574 555 m.connect(
575 556 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
576 557 action='check_revision', conditions={'method': ['GET']})
577 558
578 559 m.connect('gist', '/gists/{gist_id}',
579 560 action='show', conditions={'method': ['GET']})
580 561 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
581 562 revision='tip',
582 563 action='show', conditions={'method': ['GET']})
583 564 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
584 565 revision='tip',
585 566 action='show', conditions={'method': ['GET']})
586 567 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
587 568 revision='tip',
588 569 action='show', conditions={'method': ['GET']},
589 570 requirements=URL_NAME_REQUIREMENTS)
590 571
591 572 # ADMIN MAIN PAGES
592 573 with rmap.submapper(path_prefix=ADMIN_PREFIX,
593 574 controller='admin/admin') as m:
594 575 m.connect('admin_home', '', action='index')
595 576 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
596 577 action='add_repo')
597 578 m.connect(
598 579 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
599 580 action='pull_requests')
600 581 m.connect(
601 582 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
602 583 action='pull_requests')
603 584 m.connect(
604 585 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
605 586 action='pull_requests')
606 587
607 588 # USER JOURNAL
608 589 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
609 590 controller='journal', action='index')
610 591 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
611 592 controller='journal', action='journal_rss')
612 593 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
613 594 controller='journal', action='journal_atom')
614 595
615 596 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
616 597 controller='journal', action='public_journal')
617 598
618 599 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
619 600 controller='journal', action='public_journal_rss')
620 601
621 602 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
622 603 controller='journal', action='public_journal_rss')
623 604
624 605 rmap.connect('public_journal_atom',
625 606 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
626 607 action='public_journal_atom')
627 608
628 609 rmap.connect('public_journal_atom_old',
629 610 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
630 611 action='public_journal_atom')
631 612
632 613 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
633 614 controller='journal', action='toggle_following', jsroute=True,
634 615 conditions={'method': ['POST']})
635 616
636 617 # FULL TEXT SEARCH
637 618 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
638 619 controller='search')
639 620 rmap.connect('search_repo_home', '/{repo_name}/search',
640 621 controller='search',
641 622 action='index',
642 623 conditions={'function': check_repo},
643 624 requirements=URL_NAME_REQUIREMENTS)
644 625
645 626 # FEEDS
646 627 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
647 628 controller='feed', action='rss',
648 629 conditions={'function': check_repo},
649 630 requirements=URL_NAME_REQUIREMENTS)
650 631
651 632 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
652 633 controller='feed', action='atom',
653 634 conditions={'function': check_repo},
654 635 requirements=URL_NAME_REQUIREMENTS)
655 636
656 637 #==========================================================================
657 638 # REPOSITORY ROUTES
658 639 #==========================================================================
659 640
660 641 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
661 642 controller='admin/repos', action='repo_creating',
662 643 requirements=URL_NAME_REQUIREMENTS)
663 644 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
664 645 controller='admin/repos', action='repo_check',
665 646 requirements=URL_NAME_REQUIREMENTS)
666 647
667 648 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
668 649 controller='summary', action='repo_stats',
669 650 conditions={'function': check_repo},
670 651 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
671 652
672 653 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
673 654 controller='summary', action='repo_refs_data',
674 655 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
675 656 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
676 657 controller='summary', action='repo_refs_changelog_data',
677 658 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
678 659 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
679 660 controller='summary', action='repo_default_reviewers_data',
680 661 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
681 662
682 663 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
683 664 controller='changeset', revision='tip',
684 665 conditions={'function': check_repo},
685 666 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
686 667 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
687 668 controller='changeset', revision='tip', action='changeset_children',
688 669 conditions={'function': check_repo},
689 670 requirements=URL_NAME_REQUIREMENTS)
690 671 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
691 672 controller='changeset', revision='tip', action='changeset_parents',
692 673 conditions={'function': check_repo},
693 674 requirements=URL_NAME_REQUIREMENTS)
694 675
695 676 # repo edit options
696 677 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
697 678 controller='admin/repos', action='edit',
698 679 conditions={'method': ['GET'], 'function': check_repo},
699 680 requirements=URL_NAME_REQUIREMENTS)
700 681
701 682 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
702 683 jsroute=True,
703 684 controller='admin/repos', action='edit_permissions',
704 685 conditions={'method': ['GET'], 'function': check_repo},
705 686 requirements=URL_NAME_REQUIREMENTS)
706 687 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
707 688 controller='admin/repos', action='edit_permissions_update',
708 689 conditions={'method': ['PUT'], 'function': check_repo},
709 690 requirements=URL_NAME_REQUIREMENTS)
710 691
711 692 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
712 693 controller='admin/repos', action='edit_fields',
713 694 conditions={'method': ['GET'], 'function': check_repo},
714 695 requirements=URL_NAME_REQUIREMENTS)
715 696 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
716 697 controller='admin/repos', action='create_repo_field',
717 698 conditions={'method': ['PUT'], 'function': check_repo},
718 699 requirements=URL_NAME_REQUIREMENTS)
719 700 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
720 701 controller='admin/repos', action='delete_repo_field',
721 702 conditions={'method': ['DELETE'], 'function': check_repo},
722 703 requirements=URL_NAME_REQUIREMENTS)
723 704
724 705 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
725 706 controller='admin/repos', action='edit_advanced',
726 707 conditions={'method': ['GET'], 'function': check_repo},
727 708 requirements=URL_NAME_REQUIREMENTS)
728 709
729 710 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
730 711 controller='admin/repos', action='edit_advanced_locking',
731 712 conditions={'method': ['PUT'], 'function': check_repo},
732 713 requirements=URL_NAME_REQUIREMENTS)
733 714 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
734 715 controller='admin/repos', action='toggle_locking',
735 716 conditions={'method': ['GET'], 'function': check_repo},
736 717 requirements=URL_NAME_REQUIREMENTS)
737 718
738 719 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
739 720 controller='admin/repos', action='edit_advanced_journal',
740 721 conditions={'method': ['PUT'], 'function': check_repo},
741 722 requirements=URL_NAME_REQUIREMENTS)
742 723
743 724 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
744 725 controller='admin/repos', action='edit_advanced_fork',
745 726 conditions={'method': ['PUT'], 'function': check_repo},
746 727 requirements=URL_NAME_REQUIREMENTS)
747 728
748 729 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
749 730 controller='admin/repos', action='edit_caches_form',
750 731 conditions={'method': ['GET'], 'function': check_repo},
751 732 requirements=URL_NAME_REQUIREMENTS)
752 733 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
753 734 controller='admin/repos', action='edit_caches',
754 735 conditions={'method': ['PUT'], 'function': check_repo},
755 736 requirements=URL_NAME_REQUIREMENTS)
756 737
757 738 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
758 739 controller='admin/repos', action='edit_remote_form',
759 740 conditions={'method': ['GET'], 'function': check_repo},
760 741 requirements=URL_NAME_REQUIREMENTS)
761 742 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
762 743 controller='admin/repos', action='edit_remote',
763 744 conditions={'method': ['PUT'], 'function': check_repo},
764 745 requirements=URL_NAME_REQUIREMENTS)
765 746
766 747 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
767 748 controller='admin/repos', action='edit_statistics_form',
768 749 conditions={'method': ['GET'], 'function': check_repo},
769 750 requirements=URL_NAME_REQUIREMENTS)
770 751 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
771 752 controller='admin/repos', action='edit_statistics',
772 753 conditions={'method': ['PUT'], 'function': check_repo},
773 754 requirements=URL_NAME_REQUIREMENTS)
774 755 rmap.connect('repo_settings_issuetracker',
775 756 '/{repo_name}/settings/issue-tracker',
776 757 controller='admin/repos', action='repo_issuetracker',
777 758 conditions={'method': ['GET'], 'function': check_repo},
778 759 requirements=URL_NAME_REQUIREMENTS)
779 760 rmap.connect('repo_issuetracker_test',
780 761 '/{repo_name}/settings/issue-tracker/test',
781 762 controller='admin/repos', action='repo_issuetracker_test',
782 763 conditions={'method': ['POST'], 'function': check_repo},
783 764 requirements=URL_NAME_REQUIREMENTS)
784 765 rmap.connect('repo_issuetracker_delete',
785 766 '/{repo_name}/settings/issue-tracker/delete',
786 767 controller='admin/repos', action='repo_issuetracker_delete',
787 768 conditions={'method': ['DELETE'], 'function': check_repo},
788 769 requirements=URL_NAME_REQUIREMENTS)
789 770 rmap.connect('repo_issuetracker_save',
790 771 '/{repo_name}/settings/issue-tracker/save',
791 772 controller='admin/repos', action='repo_issuetracker_save',
792 773 conditions={'method': ['POST'], 'function': check_repo},
793 774 requirements=URL_NAME_REQUIREMENTS)
794 775 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
795 776 controller='admin/repos', action='repo_settings_vcs_update',
796 777 conditions={'method': ['POST'], 'function': check_repo},
797 778 requirements=URL_NAME_REQUIREMENTS)
798 779 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
799 780 controller='admin/repos', action='repo_settings_vcs',
800 781 conditions={'method': ['GET'], 'function': check_repo},
801 782 requirements=URL_NAME_REQUIREMENTS)
802 783 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
803 784 controller='admin/repos', action='repo_delete_svn_pattern',
804 785 conditions={'method': ['DELETE'], 'function': check_repo},
805 786 requirements=URL_NAME_REQUIREMENTS)
806 787 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
807 788 controller='admin/repos', action='repo_settings_pullrequest',
808 789 conditions={'method': ['GET', 'POST'], 'function': check_repo},
809 790 requirements=URL_NAME_REQUIREMENTS)
810 791
811 792 # still working url for backward compat.
812 793 rmap.connect('raw_changeset_home_depraced',
813 794 '/{repo_name}/raw-changeset/{revision}',
814 795 controller='changeset', action='changeset_raw',
815 796 revision='tip', conditions={'function': check_repo},
816 797 requirements=URL_NAME_REQUIREMENTS)
817 798
818 799 # new URLs
819 800 rmap.connect('changeset_raw_home',
820 801 '/{repo_name}/changeset-diff/{revision}',
821 802 controller='changeset', action='changeset_raw',
822 803 revision='tip', conditions={'function': check_repo},
823 804 requirements=URL_NAME_REQUIREMENTS)
824 805
825 806 rmap.connect('changeset_patch_home',
826 807 '/{repo_name}/changeset-patch/{revision}',
827 808 controller='changeset', action='changeset_patch',
828 809 revision='tip', conditions={'function': check_repo},
829 810 requirements=URL_NAME_REQUIREMENTS)
830 811
831 812 rmap.connect('changeset_download_home',
832 813 '/{repo_name}/changeset-download/{revision}',
833 814 controller='changeset', action='changeset_download',
834 815 revision='tip', conditions={'function': check_repo},
835 816 requirements=URL_NAME_REQUIREMENTS)
836 817
837 818 rmap.connect('changeset_comment',
838 819 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
839 820 controller='changeset', revision='tip', action='comment',
840 821 conditions={'function': check_repo},
841 822 requirements=URL_NAME_REQUIREMENTS)
842 823
843 824 rmap.connect('changeset_comment_preview',
844 825 '/{repo_name}/changeset/comment/preview', jsroute=True,
845 826 controller='changeset', action='preview_comment',
846 827 conditions={'function': check_repo, 'method': ['POST']},
847 828 requirements=URL_NAME_REQUIREMENTS)
848 829
849 830 rmap.connect('changeset_comment_delete',
850 831 '/{repo_name}/changeset/comment/{comment_id}/delete',
851 832 controller='changeset', action='delete_comment',
852 833 conditions={'function': check_repo, 'method': ['DELETE']},
853 834 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
854 835
855 836 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
856 837 controller='changeset', action='changeset_info',
857 838 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
858 839
859 840 rmap.connect('compare_home',
860 841 '/{repo_name}/compare',
861 842 controller='compare', action='index',
862 843 conditions={'function': check_repo},
863 844 requirements=URL_NAME_REQUIREMENTS)
864 845
865 846 rmap.connect('compare_url',
866 847 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
867 848 controller='compare', action='compare',
868 849 conditions={'function': check_repo},
869 850 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
870 851
871 852 rmap.connect('pullrequest_home',
872 853 '/{repo_name}/pull-request/new', controller='pullrequests',
873 854 action='index', conditions={'function': check_repo,
874 855 'method': ['GET']},
875 856 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
876 857
877 858 rmap.connect('pullrequest',
878 859 '/{repo_name}/pull-request/new', controller='pullrequests',
879 860 action='create', conditions={'function': check_repo,
880 861 'method': ['POST']},
881 862 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
882 863
883 864 rmap.connect('pullrequest_repo_refs',
884 865 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
885 866 controller='pullrequests',
886 867 action='get_repo_refs',
887 868 conditions={'function': check_repo, 'method': ['GET']},
888 869 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
889 870
890 871 rmap.connect('pullrequest_repo_destinations',
891 872 '/{repo_name}/pull-request/repo-destinations',
892 873 controller='pullrequests',
893 874 action='get_repo_destinations',
894 875 conditions={'function': check_repo, 'method': ['GET']},
895 876 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
896 877
897 878 rmap.connect('pullrequest_show',
898 879 '/{repo_name}/pull-request/{pull_request_id}',
899 880 controller='pullrequests',
900 881 action='show', conditions={'function': check_repo,
901 882 'method': ['GET']},
902 883 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
903 884
904 885 rmap.connect('pullrequest_update',
905 886 '/{repo_name}/pull-request/{pull_request_id}',
906 887 controller='pullrequests',
907 888 action='update', conditions={'function': check_repo,
908 889 'method': ['PUT']},
909 890 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
910 891
911 892 rmap.connect('pullrequest_merge',
912 893 '/{repo_name}/pull-request/{pull_request_id}',
913 894 controller='pullrequests',
914 895 action='merge', conditions={'function': check_repo,
915 896 'method': ['POST']},
916 897 requirements=URL_NAME_REQUIREMENTS)
917 898
918 899 rmap.connect('pullrequest_delete',
919 900 '/{repo_name}/pull-request/{pull_request_id}',
920 901 controller='pullrequests',
921 902 action='delete', conditions={'function': check_repo,
922 903 'method': ['DELETE']},
923 904 requirements=URL_NAME_REQUIREMENTS)
924 905
925 906 rmap.connect('pullrequest_show_all',
926 907 '/{repo_name}/pull-request',
927 908 controller='pullrequests',
928 909 action='show_all', conditions={'function': check_repo,
929 910 'method': ['GET']},
930 911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
931 912
932 913 rmap.connect('pullrequest_comment',
933 914 '/{repo_name}/pull-request-comment/{pull_request_id}',
934 915 controller='pullrequests',
935 916 action='comment', conditions={'function': check_repo,
936 917 'method': ['POST']},
937 918 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
938 919
939 920 rmap.connect('pullrequest_comment_delete',
940 921 '/{repo_name}/pull-request-comment/{comment_id}/delete',
941 922 controller='pullrequests', action='delete_comment',
942 923 conditions={'function': check_repo, 'method': ['DELETE']},
943 924 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
944 925
945 926 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
946 927 controller='summary', conditions={'function': check_repo},
947 928 requirements=URL_NAME_REQUIREMENTS)
948 929
949 930 rmap.connect('branches_home', '/{repo_name}/branches',
950 931 controller='branches', conditions={'function': check_repo},
951 932 requirements=URL_NAME_REQUIREMENTS)
952 933
953 934 rmap.connect('tags_home', '/{repo_name}/tags',
954 935 controller='tags', conditions={'function': check_repo},
955 936 requirements=URL_NAME_REQUIREMENTS)
956 937
957 938 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
958 939 controller='bookmarks', conditions={'function': check_repo},
959 940 requirements=URL_NAME_REQUIREMENTS)
960 941
961 942 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
962 943 controller='changelog', conditions={'function': check_repo},
963 944 requirements=URL_NAME_REQUIREMENTS)
964 945
965 946 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
966 947 controller='changelog', action='changelog_summary',
967 948 conditions={'function': check_repo},
968 949 requirements=URL_NAME_REQUIREMENTS)
969 950
970 951 rmap.connect('changelog_file_home',
971 952 '/{repo_name}/changelog/{revision}/{f_path}',
972 953 controller='changelog', f_path=None,
973 954 conditions={'function': check_repo},
974 955 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
975 956
976 957 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
977 958 controller='changelog', action='changelog_elements',
978 959 conditions={'function': check_repo},
979 960 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
980 961
981 962 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
982 963 controller='files', revision='tip', f_path='',
983 964 conditions={'function': check_repo},
984 965 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
985 966
986 967 rmap.connect('files_home_simple_catchrev',
987 968 '/{repo_name}/files/{revision}',
988 969 controller='files', revision='tip', f_path='',
989 970 conditions={'function': check_repo},
990 971 requirements=URL_NAME_REQUIREMENTS)
991 972
992 973 rmap.connect('files_home_simple_catchall',
993 974 '/{repo_name}/files',
994 975 controller='files', revision='tip', f_path='',
995 976 conditions={'function': check_repo},
996 977 requirements=URL_NAME_REQUIREMENTS)
997 978
998 979 rmap.connect('files_history_home',
999 980 '/{repo_name}/history/{revision}/{f_path}',
1000 981 controller='files', action='history', revision='tip', f_path='',
1001 982 conditions={'function': check_repo},
1002 983 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1003 984
1004 985 rmap.connect('files_authors_home',
1005 986 '/{repo_name}/authors/{revision}/{f_path}',
1006 987 controller='files', action='authors', revision='tip', f_path='',
1007 988 conditions={'function': check_repo},
1008 989 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1009 990
1010 991 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1011 992 controller='files', action='diff', f_path='',
1012 993 conditions={'function': check_repo},
1013 994 requirements=URL_NAME_REQUIREMENTS)
1014 995
1015 996 rmap.connect('files_diff_2way_home',
1016 997 '/{repo_name}/diff-2way/{f_path}',
1017 998 controller='files', action='diff_2way', f_path='',
1018 999 conditions={'function': check_repo},
1019 1000 requirements=URL_NAME_REQUIREMENTS)
1020 1001
1021 1002 rmap.connect('files_rawfile_home',
1022 1003 '/{repo_name}/rawfile/{revision}/{f_path}',
1023 1004 controller='files', action='rawfile', revision='tip',
1024 1005 f_path='', conditions={'function': check_repo},
1025 1006 requirements=URL_NAME_REQUIREMENTS)
1026 1007
1027 1008 rmap.connect('files_raw_home',
1028 1009 '/{repo_name}/raw/{revision}/{f_path}',
1029 1010 controller='files', action='raw', revision='tip', f_path='',
1030 1011 conditions={'function': check_repo},
1031 1012 requirements=URL_NAME_REQUIREMENTS)
1032 1013
1033 1014 rmap.connect('files_render_home',
1034 1015 '/{repo_name}/render/{revision}/{f_path}',
1035 1016 controller='files', action='index', revision='tip', f_path='',
1036 1017 rendered=True, conditions={'function': check_repo},
1037 1018 requirements=URL_NAME_REQUIREMENTS)
1038 1019
1039 1020 rmap.connect('files_annotate_home',
1040 1021 '/{repo_name}/annotate/{revision}/{f_path}',
1041 1022 controller='files', action='index', revision='tip',
1042 1023 f_path='', annotate=True, conditions={'function': check_repo},
1043 1024 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1044 1025
1045 1026 rmap.connect('files_annotate_previous',
1046 1027 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1047 1028 controller='files', action='annotate_previous', revision='tip',
1048 1029 f_path='', annotate=True, conditions={'function': check_repo},
1049 1030 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1050 1031
1051 1032 rmap.connect('files_edit',
1052 1033 '/{repo_name}/edit/{revision}/{f_path}',
1053 1034 controller='files', action='edit', revision='tip',
1054 1035 f_path='',
1055 1036 conditions={'function': check_repo, 'method': ['POST']},
1056 1037 requirements=URL_NAME_REQUIREMENTS)
1057 1038
1058 1039 rmap.connect('files_edit_home',
1059 1040 '/{repo_name}/edit/{revision}/{f_path}',
1060 1041 controller='files', action='edit_home', revision='tip',
1061 1042 f_path='', conditions={'function': check_repo},
1062 1043 requirements=URL_NAME_REQUIREMENTS)
1063 1044
1064 1045 rmap.connect('files_add',
1065 1046 '/{repo_name}/add/{revision}/{f_path}',
1066 1047 controller='files', action='add', revision='tip',
1067 1048 f_path='',
1068 1049 conditions={'function': check_repo, 'method': ['POST']},
1069 1050 requirements=URL_NAME_REQUIREMENTS)
1070 1051
1071 1052 rmap.connect('files_add_home',
1072 1053 '/{repo_name}/add/{revision}/{f_path}',
1073 1054 controller='files', action='add_home', revision='tip',
1074 1055 f_path='', conditions={'function': check_repo},
1075 1056 requirements=URL_NAME_REQUIREMENTS)
1076 1057
1077 1058 rmap.connect('files_delete',
1078 1059 '/{repo_name}/delete/{revision}/{f_path}',
1079 1060 controller='files', action='delete', revision='tip',
1080 1061 f_path='',
1081 1062 conditions={'function': check_repo, 'method': ['POST']},
1082 1063 requirements=URL_NAME_REQUIREMENTS)
1083 1064
1084 1065 rmap.connect('files_delete_home',
1085 1066 '/{repo_name}/delete/{revision}/{f_path}',
1086 1067 controller='files', action='delete_home', revision='tip',
1087 1068 f_path='', conditions={'function': check_repo},
1088 1069 requirements=URL_NAME_REQUIREMENTS)
1089 1070
1090 1071 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1091 1072 controller='files', action='archivefile',
1092 1073 conditions={'function': check_repo},
1093 1074 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1094 1075
1095 1076 rmap.connect('files_nodelist_home',
1096 1077 '/{repo_name}/nodelist/{revision}/{f_path}',
1097 1078 controller='files', action='nodelist',
1098 1079 conditions={'function': check_repo},
1099 1080 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1100 1081
1101 1082 rmap.connect('files_nodetree_full',
1102 1083 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1103 1084 controller='files', action='nodetree_full',
1104 1085 conditions={'function': check_repo},
1105 1086 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1106 1087
1107 1088 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1108 1089 controller='forks', action='fork_create',
1109 1090 conditions={'function': check_repo, 'method': ['POST']},
1110 1091 requirements=URL_NAME_REQUIREMENTS)
1111 1092
1112 1093 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1113 1094 controller='forks', action='fork',
1114 1095 conditions={'function': check_repo},
1115 1096 requirements=URL_NAME_REQUIREMENTS)
1116 1097
1117 1098 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1118 1099 controller='forks', action='forks',
1119 1100 conditions={'function': check_repo},
1120 1101 requirements=URL_NAME_REQUIREMENTS)
1121 1102
1122 1103 # must be here for proper group/repo catching pattern
1123 1104 _connect_with_slash(
1124 1105 rmap, 'repo_group_home', '/{group_name}',
1125 1106 controller='home', action='index_repo_group',
1126 1107 conditions={'function': check_group},
1127 1108 requirements=URL_NAME_REQUIREMENTS)
1128 1109
1129 1110 # catch all, at the end
1130 1111 _connect_with_slash(
1131 1112 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1132 1113 controller='summary', action='index',
1133 1114 conditions={'function': check_repo},
1134 1115 requirements=URL_NAME_REQUIREMENTS)
1135 1116
1136 1117 return rmap
1137 1118
1138 1119
1139 1120 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1140 1121 """
1141 1122 Connect a route with an optional trailing slash in `path`.
1142 1123 """
1143 1124 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1144 1125 mapper.connect(name, path, *args, **kwargs)
@@ -1,72 +1,101 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Single source for redirection links.
23 23
24 24 Goal of this module is to provide a single source of truth regarding external
25 25 links. The data inside this module is used to configure the routing
26 26 system of Enterprise and it is used also as a base to check if this data
27 27 and our server configuration are in sync.
28 28
29 29 .. py:data:: link_config
30 30
31 31 Contains the configuration for external links. Each item is supposed to be
32 32 a `dict` like this example::
33 33
34 34 {"name": "url_name",
35 35 "target": "https://rhodecode.com/r1/enterprise/keyword/",
36 36 "external_target": "https://example.com/some-page.html",
37 37 }
38 38
39 39 then you can retrieve the url by simply calling the URL function:
40 40
41 41 `h.url('url_name')`
42 42
43 43 The redirection must be first implemented in our servers before
44 44 you can see it working.
45 45 """
46 46 # flake8: noqa
47 47 from __future__ import unicode_literals
48 48
49 link_config = [
50 {
51 "name": "enterprise_docs",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 },
55 {
56 "name": "enterprise_log_file_locations",
57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
59 },
60 {
61 "name": "enterprise_issue_tracker_settings",
62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
64 },
65 {
66 "name": "enterprise_svn_setup",
67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
69 },
70 {
71 "name": "rst_help",
72 "target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
73 "external_target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
74 },
75 {
76 "name": "markdown_help",
77 "target": "http://daringfireball.net/projects/markdown/syntax",
78 "external_target": "http://daringfireball.net/projects/markdown/syntax",
79 },
80 {
81 "name": "rhodecode_official",
82 "target": "https://rhodecode.com",
83 "external_target": "https://rhodecode.com/",
84 },
85 {
86 "name": "rhodecode_support",
87 "target": "https://rhodecode.com/help/",
88 "external_target": "https://rhodecode.com/support",
89 },
90 {
91 "name": "rhodecode_translations",
92 "target": "https://rhodecode.com/translate/enterprise",
93 "external_target": "https://www.transifex.com/rhodecode/RhodeCode/",
94 },
49 95
50 link_config = [
51 {"name": "enterprise_docs",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 },
55 {"name": "enterprise_log_file_locations",
56 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
57 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
58 },
59 {"name": "enterprise_issue_tracker_settings",
60 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
61 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
62 },
63 {"name": "enterprise_svn_setup",
64 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
65 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
66 },
67 96 ]
68 97
69 98
70 def connect_redirection_links(rmap):
99 def connect_redirection_links(config):
71 100 for link in link_config:
72 rmap.connect(link['name'], link['target'], _static=True)
101 config.add_route(link['name'], link['target'], static=True)
@@ -1,589 +1,589 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import ipaddress
31 31 import pyramid.threadlocal
32 32
33 33 from paste.auth.basic import AuthBasicAuthenticator
34 34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 36 from pylons import config, tmpl_context as c, request, session, url
37 37 from pylons.controllers import WSGIController
38 38 from pylons.controllers.util import redirect
39 39 from pylons.i18n import translation
40 40 # marcink: don't remove this import
41 41 from pylons.templating import render_mako as render # noqa
42 42 from pylons.i18n.translation import _
43 43 from webob.exc import HTTPFound
44 44
45 45
46 46 import rhodecode
47 47 from rhodecode.authentication.base import VCS_TYPE
48 48 from rhodecode.lib import auth, utils2
49 49 from rhodecode.lib import helpers as h
50 50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 51 from rhodecode.lib.exceptions import UserCreationError
52 52 from rhodecode.lib.utils import (
53 53 get_repo_slug, set_rhodecode_config, password_changed,
54 54 get_enabled_hook_classes)
55 55 from rhodecode.lib.utils2 import (
56 56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 58 from rhodecode.model import meta
59 59 from rhodecode.model.db import Repository, User, ChangesetComment
60 60 from rhodecode.model.notification import NotificationModel
61 61 from rhodecode.model.scm import ScmModel
62 62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63 63
64 64
65 65 log = logging.getLogger(__name__)
66 66
67 67
68 68 def _filter_proxy(ip):
69 69 """
70 70 Passed in IP addresses in HEADERS can be in a special format of multiple
71 71 ips. Those comma separated IPs are passed from various proxies in the
72 72 chain of request processing. The left-most being the original client.
73 73 We only care about the first IP which came from the org. client.
74 74
75 75 :param ip: ip string from headers
76 76 """
77 77 if ',' in ip:
78 78 _ips = ip.split(',')
79 79 _first_ip = _ips[0].strip()
80 80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
81 81 return _first_ip
82 82 return ip
83 83
84 84
85 85 def _filter_port(ip):
86 86 """
87 87 Removes a port from ip, there are 4 main cases to handle here.
88 88 - ipv4 eg. 127.0.0.1
89 89 - ipv6 eg. ::1
90 90 - ipv4+port eg. 127.0.0.1:8080
91 91 - ipv6+port eg. [::1]:8080
92 92
93 93 :param ip:
94 94 """
95 95 def is_ipv6(ip_addr):
96 96 if hasattr(socket, 'inet_pton'):
97 97 try:
98 98 socket.inet_pton(socket.AF_INET6, ip_addr)
99 99 except socket.error:
100 100 return False
101 101 else:
102 102 # fallback to ipaddress
103 103 try:
104 104 ipaddress.IPv6Address(ip_addr)
105 105 except Exception:
106 106 return False
107 107 return True
108 108
109 109 if ':' not in ip: # must be ipv4 pure ip
110 110 return ip
111 111
112 112 if '[' in ip and ']' in ip: # ipv6 with port
113 113 return ip.split(']')[0][1:].lower()
114 114
115 115 # must be ipv6 or ipv4 with port
116 116 if is_ipv6(ip):
117 117 return ip
118 118 else:
119 119 ip, _port = ip.split(':')[:2] # means ipv4+port
120 120 return ip
121 121
122 122
123 123 def get_ip_addr(environ):
124 124 proxy_key = 'HTTP_X_REAL_IP'
125 125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
126 126 def_key = 'REMOTE_ADDR'
127 127 _filters = lambda x: _filter_port(_filter_proxy(x))
128 128
129 129 ip = environ.get(proxy_key)
130 130 if ip:
131 131 return _filters(ip)
132 132
133 133 ip = environ.get(proxy_key2)
134 134 if ip:
135 135 return _filters(ip)
136 136
137 137 ip = environ.get(def_key, '0.0.0.0')
138 138 return _filters(ip)
139 139
140 140
141 141 def get_server_ip_addr(environ, log_errors=True):
142 142 hostname = environ.get('SERVER_NAME')
143 143 try:
144 144 return socket.gethostbyname(hostname)
145 145 except Exception as e:
146 146 if log_errors:
147 147 # in some cases this lookup is not possible, and we don't want to
148 148 # make it an exception in logs
149 149 log.exception('Could not retrieve server ip address: %s', e)
150 150 return hostname
151 151
152 152
153 153 def get_server_port(environ):
154 154 return environ.get('SERVER_PORT')
155 155
156 156
157 157 def get_access_path(environ):
158 158 path = environ.get('PATH_INFO')
159 159 org_req = environ.get('pylons.original_request')
160 160 if org_req:
161 161 path = org_req.environ.get('PATH_INFO')
162 162 return path
163 163
164 164
165 165 def vcs_operation_context(
166 166 environ, repo_name, username, action, scm, check_locking=True,
167 167 is_shadow_repo=False):
168 168 """
169 169 Generate the context for a vcs operation, e.g. push or pull.
170 170
171 171 This context is passed over the layers so that hooks triggered by the
172 172 vcs operation know details like the user, the user's IP address etc.
173 173
174 174 :param check_locking: Allows to switch of the computation of the locking
175 175 data. This serves mainly the need of the simplevcs middleware to be
176 176 able to disable this for certain operations.
177 177
178 178 """
179 179 # Tri-state value: False: unlock, None: nothing, True: lock
180 180 make_lock = None
181 181 locked_by = [None, None, None]
182 182 is_anonymous = username == User.DEFAULT_USER
183 183 if not is_anonymous and check_locking:
184 184 log.debug('Checking locking on repository "%s"', repo_name)
185 185 user = User.get_by_username(username)
186 186 repo = Repository.get_by_repo_name(repo_name)
187 187 make_lock, __, locked_by = repo.get_locking_state(
188 188 action, user.user_id)
189 189
190 190 settings_model = VcsSettingsModel(repo=repo_name)
191 191 ui_settings = settings_model.get_ui_settings()
192 192
193 193 extras = {
194 194 'ip': get_ip_addr(environ),
195 195 'username': username,
196 196 'action': action,
197 197 'repository': repo_name,
198 198 'scm': scm,
199 199 'config': rhodecode.CONFIG['__file__'],
200 200 'make_lock': make_lock,
201 201 'locked_by': locked_by,
202 202 'server_url': utils2.get_server_url(environ),
203 203 'hooks': get_enabled_hook_classes(ui_settings),
204 204 'is_shadow_repo': is_shadow_repo,
205 205 }
206 206 return extras
207 207
208 208
209 209 class BasicAuth(AuthBasicAuthenticator):
210 210
211 211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
212 212 initial_call_detection=False, acl_repo_name=None):
213 213 self.realm = realm
214 214 self.initial_call = initial_call_detection
215 215 self.authfunc = authfunc
216 216 self.registry = registry
217 217 self.acl_repo_name = acl_repo_name
218 218 self._rc_auth_http_code = auth_http_code
219 219
220 220 def _get_response_from_code(self, http_code):
221 221 try:
222 222 return get_exception(safe_int(http_code))
223 223 except Exception:
224 224 log.exception('Failed to fetch response for code %s' % http_code)
225 225 return HTTPForbidden
226 226
227 227 def build_authentication(self):
228 228 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
229 229 if self._rc_auth_http_code and not self.initial_call:
230 230 # return alternative HTTP code if alternative http return code
231 231 # is specified in RhodeCode config, but ONLY if it's not the
232 232 # FIRST call
233 233 custom_response_klass = self._get_response_from_code(
234 234 self._rc_auth_http_code)
235 235 return custom_response_klass(headers=head)
236 236 return HTTPUnauthorized(headers=head)
237 237
238 238 def authenticate(self, environ):
239 239 authorization = AUTHORIZATION(environ)
240 240 if not authorization:
241 241 return self.build_authentication()
242 242 (authmeth, auth) = authorization.split(' ', 1)
243 243 if 'basic' != authmeth.lower():
244 244 return self.build_authentication()
245 245 auth = auth.strip().decode('base64')
246 246 _parts = auth.split(':', 1)
247 247 if len(_parts) == 2:
248 248 username, password = _parts
249 249 if self.authfunc(
250 250 username, password, environ, VCS_TYPE,
251 251 registry=self.registry, acl_repo_name=self.acl_repo_name):
252 252 return username
253 253 if username and password:
254 254 # we mark that we actually executed authentication once, at
255 255 # that point we can use the alternative auth code
256 256 self.initial_call = False
257 257
258 258 return self.build_authentication()
259 259
260 260 __call__ = authenticate
261 261
262 262
263 263 def attach_context_attributes(context, request):
264 264 """
265 265 Attach variables into template context called `c`, please note that
266 266 request could be pylons or pyramid request in here.
267 267 """
268 268 rc_config = SettingsModel().get_all_settings(cache=True)
269 269
270 270 context.rhodecode_version = rhodecode.__version__
271 271 context.rhodecode_edition = config.get('rhodecode.edition')
272 272 # unique secret + version does not leak the version but keep consistency
273 273 context.rhodecode_version_hash = md5(
274 274 config.get('beaker.session.secret', '') +
275 275 rhodecode.__version__)[:8]
276 276
277 277 # Default language set for the incoming request
278 278 context.language = translation.get_lang()[0]
279 279
280 280 # Visual options
281 281 context.visual = AttributeDict({})
282 282
283 283 # DB stored Visual Items
284 284 context.visual.show_public_icon = str2bool(
285 285 rc_config.get('rhodecode_show_public_icon'))
286 286 context.visual.show_private_icon = str2bool(
287 287 rc_config.get('rhodecode_show_private_icon'))
288 288 context.visual.stylify_metatags = str2bool(
289 289 rc_config.get('rhodecode_stylify_metatags'))
290 290 context.visual.dashboard_items = safe_int(
291 291 rc_config.get('rhodecode_dashboard_items', 100))
292 292 context.visual.admin_grid_items = safe_int(
293 293 rc_config.get('rhodecode_admin_grid_items', 100))
294 294 context.visual.repository_fields = str2bool(
295 295 rc_config.get('rhodecode_repository_fields'))
296 296 context.visual.show_version = str2bool(
297 297 rc_config.get('rhodecode_show_version'))
298 298 context.visual.use_gravatar = str2bool(
299 299 rc_config.get('rhodecode_use_gravatar'))
300 300 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
301 301 context.visual.default_renderer = rc_config.get(
302 302 'rhodecode_markup_renderer', 'rst')
303 303 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
304 304 context.visual.rhodecode_support_url = \
305 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
305 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
306 306
307 307 context.pre_code = rc_config.get('rhodecode_pre_code')
308 308 context.post_code = rc_config.get('rhodecode_post_code')
309 309 context.rhodecode_name = rc_config.get('rhodecode_title')
310 310 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
311 311 # if we have specified default_encoding in the request, it has more
312 312 # priority
313 313 if request.GET.get('default_encoding'):
314 314 context.default_encodings.insert(0, request.GET.get('default_encoding'))
315 315 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
316 316
317 317 # INI stored
318 318 context.labs_active = str2bool(
319 319 config.get('labs_settings_active', 'false'))
320 320 context.visual.allow_repo_location_change = str2bool(
321 321 config.get('allow_repo_location_change', True))
322 322 context.visual.allow_custom_hooks_settings = str2bool(
323 323 config.get('allow_custom_hooks_settings', True))
324 324 context.debug_style = str2bool(config.get('debug_style', False))
325 325
326 326 context.rhodecode_instanceid = config.get('instance_id')
327 327
328 328 # AppEnlight
329 329 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
330 330 context.appenlight_api_public_key = config.get(
331 331 'appenlight.api_public_key', '')
332 332 context.appenlight_server_url = config.get('appenlight.server_url', '')
333 333
334 334 # JS template context
335 335 context.template_context = {
336 336 'repo_name': None,
337 337 'repo_type': None,
338 338 'repo_landing_commit': None,
339 339 'rhodecode_user': {
340 340 'username': None,
341 341 'email': None,
342 342 'notification_status': False
343 343 },
344 344 'visual': {
345 345 'default_renderer': None
346 346 },
347 347 'commit_data': {
348 348 'commit_id': None
349 349 },
350 350 'pull_request_data': {'pull_request_id': None},
351 351 'timeago': {
352 352 'refresh_time': 120 * 1000,
353 353 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
354 354 },
355 355 'pylons_dispatch': {
356 356 # 'controller': request.environ['pylons.routes_dict']['controller'],
357 357 # 'action': request.environ['pylons.routes_dict']['action'],
358 358 },
359 359 'pyramid_dispatch': {
360 360
361 361 },
362 362 'extra': {'plugins': {}}
363 363 }
364 364 # END CONFIG VARS
365 365
366 366 # TODO: This dosn't work when called from pylons compatibility tween.
367 367 # Fix this and remove it from base controller.
368 368 # context.repo_name = get_repo_slug(request) # can be empty
369 369
370 370 diffmode = 'sideside'
371 371 if request.GET.get('diffmode'):
372 372 if request.GET['diffmode'] == 'unified':
373 373 diffmode = 'unified'
374 374 elif request.session.get('diffmode'):
375 375 diffmode = request.session['diffmode']
376 376
377 377 context.diffmode = diffmode
378 378
379 379 if request.session.get('diffmode') != diffmode:
380 380 request.session['diffmode'] = diffmode
381 381
382 382 context.csrf_token = auth.get_csrf_token()
383 383 context.backends = rhodecode.BACKENDS.keys()
384 384 context.backends.sort()
385 385 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
386 386 context.rhodecode_user.user_id)
387 387
388 388 context.pyramid_request = pyramid.threadlocal.get_current_request()
389 389
390 390
391 391 def get_auth_user(environ):
392 392 ip_addr = get_ip_addr(environ)
393 393 # make sure that we update permissions each time we call controller
394 394 _auth_token = (request.GET.get('auth_token', '') or
395 395 request.GET.get('api_key', ''))
396 396
397 397 if _auth_token:
398 398 # when using API_KEY we assume user exists, and
399 399 # doesn't need auth based on cookies.
400 400 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
401 401 authenticated = False
402 402 else:
403 403 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
404 404 try:
405 405 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
406 406 ip_addr=ip_addr)
407 407 except UserCreationError as e:
408 408 h.flash(e, 'error')
409 409 # container auth or other auth functions that create users
410 410 # on the fly can throw this exception signaling that there's
411 411 # issue with user creation, explanation should be provided
412 412 # in Exception itself. We then create a simple blank
413 413 # AuthUser
414 414 auth_user = AuthUser(ip_addr=ip_addr)
415 415
416 416 if password_changed(auth_user, session):
417 417 session.invalidate()
418 418 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
419 419 auth_user = AuthUser(ip_addr=ip_addr)
420 420
421 421 authenticated = cookie_store.get('is_authenticated')
422 422
423 423 if not auth_user.is_authenticated and auth_user.is_user_object:
424 424 # user is not authenticated and not empty
425 425 auth_user.set_authenticated(authenticated)
426 426
427 427 return auth_user
428 428
429 429
430 430 class BaseController(WSGIController):
431 431
432 432 def __before__(self):
433 433 """
434 434 __before__ is called before controller methods and after __call__
435 435 """
436 436 # on each call propagate settings calls into global settings.
437 437 set_rhodecode_config(config)
438 438 attach_context_attributes(c, request)
439 439
440 440 # TODO: Remove this when fixed in attach_context_attributes()
441 441 c.repo_name = get_repo_slug(request) # can be empty
442 442
443 443 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
444 444 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
445 445 self.sa = meta.Session
446 446 self.scm_model = ScmModel(self.sa)
447 447
448 448 # set user language
449 449 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
450 450 if user_lang:
451 451 translation.set_lang(user_lang)
452 452 log.debug('set language to %s for user %s',
453 453 user_lang, self._rhodecode_user)
454 454
455 455 def _dispatch_redirect(self, with_url, environ, start_response):
456 456 resp = HTTPFound(with_url)
457 457 environ['SCRIPT_NAME'] = '' # handle prefix middleware
458 458 environ['PATH_INFO'] = with_url
459 459 return resp(environ, start_response)
460 460
461 461 def __call__(self, environ, start_response):
462 462 """Invoke the Controller"""
463 463 # WSGIController.__call__ dispatches to the Controller method
464 464 # the request is routed to. This routing information is
465 465 # available in environ['pylons.routes_dict']
466 466 from rhodecode.lib import helpers as h
467 467
468 468 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
469 469 if environ.get('debugtoolbar.wants_pylons_context', False):
470 470 environ['debugtoolbar.pylons_context'] = c._current_obj()
471 471
472 472 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
473 473 environ['pylons.routes_dict']['action']])
474 474
475 475 self.rc_config = SettingsModel().get_all_settings(cache=True)
476 476 self.ip_addr = get_ip_addr(environ)
477 477
478 478 # The rhodecode auth user is looked up and passed through the
479 479 # environ by the pylons compatibility tween in pyramid.
480 480 # So we can just grab it from there.
481 481 auth_user = environ['rc_auth_user']
482 482
483 483 # set globals for auth user
484 484 request.user = auth_user
485 485 c.rhodecode_user = self._rhodecode_user = auth_user
486 486
487 487 log.info('IP: %s User: %s accessed %s [%s]' % (
488 488 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
489 489 _route_name)
490 490 )
491 491
492 492 user_obj = auth_user.get_instance()
493 493 if user_obj and user_obj.user_data.get('force_password_change'):
494 494 h.flash('You are required to change your password', 'warning',
495 495 ignore_duplicate=True)
496 496 return self._dispatch_redirect(
497 497 url('my_account_password'), environ, start_response)
498 498
499 499 return WSGIController.__call__(self, environ, start_response)
500 500
501 501
502 502 class BaseRepoController(BaseController):
503 503 """
504 504 Base class for controllers responsible for loading all needed data for
505 505 repository loaded items are
506 506
507 507 c.rhodecode_repo: instance of scm repository
508 508 c.rhodecode_db_repo: instance of db
509 509 c.repository_requirements_missing: shows that repository specific data
510 510 could not be displayed due to the missing requirements
511 511 c.repository_pull_requests: show number of open pull requests
512 512 """
513 513
514 514 def __before__(self):
515 515 super(BaseRepoController, self).__before__()
516 516 if c.repo_name: # extracted from routes
517 517 db_repo = Repository.get_by_repo_name(c.repo_name)
518 518 if not db_repo:
519 519 return
520 520
521 521 log.debug(
522 522 'Found repository in database %s with state `%s`',
523 523 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
524 524 route = getattr(request.environ.get('routes.route'), 'name', '')
525 525
526 526 # allow to delete repos that are somehow damages in filesystem
527 527 if route in ['delete_repo']:
528 528 return
529 529
530 530 if db_repo.repo_state in [Repository.STATE_PENDING]:
531 531 if route in ['repo_creating_home']:
532 532 return
533 533 check_url = url('repo_creating_home', repo_name=c.repo_name)
534 534 return redirect(check_url)
535 535
536 536 self.rhodecode_db_repo = db_repo
537 537
538 538 missing_requirements = False
539 539 try:
540 540 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
541 541 except RepositoryRequirementError as e:
542 542 missing_requirements = True
543 543 self._handle_missing_requirements(e)
544 544
545 545 if self.rhodecode_repo is None and not missing_requirements:
546 546 log.error('%s this repository is present in database but it '
547 547 'cannot be created as an scm instance', c.repo_name)
548 548
549 549 h.flash(_(
550 550 "The repository at %(repo_name)s cannot be located.") %
551 551 {'repo_name': c.repo_name},
552 552 category='error', ignore_duplicate=True)
553 553 redirect(url('home'))
554 554
555 555 # update last change according to VCS data
556 556 if not missing_requirements:
557 557 commit = db_repo.get_commit(
558 558 pre_load=["author", "date", "message", "parents"])
559 559 db_repo.update_commit_cache(commit)
560 560
561 561 # Prepare context
562 562 c.rhodecode_db_repo = db_repo
563 563 c.rhodecode_repo = self.rhodecode_repo
564 564 c.repository_requirements_missing = missing_requirements
565 565
566 566 self._update_global_counters(self.scm_model, db_repo)
567 567
568 568 def _update_global_counters(self, scm_model, db_repo):
569 569 """
570 570 Base variables that are exposed to every page of repository
571 571 """
572 572 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
573 573
574 574 def _handle_missing_requirements(self, error):
575 575 self.rhodecode_repo = None
576 576 log.error(
577 577 'Requirements are missing for repository %s: %s',
578 578 c.repo_name, error.message)
579 579
580 580 summary_url = url('summary_home', repo_name=c.repo_name)
581 581 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
582 582 settings_update_url = url('repo', repo_name=c.repo_name)
583 583 path = request.path
584 584 should_redirect = (
585 585 path not in (summary_url, settings_update_url)
586 586 and '/settings' not in path or path == statistics_url
587 587 )
588 588 if should_redirect:
589 589 redirect(summary_url)
@@ -1,150 +1,150 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default user-profile">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('User Profile')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="user-profile-content">
9 9 ${h.secure_form(url('update_user', user_id=c.user.user_id),method='put', class_='form')}
10 10 <% readonly = None %>
11 11 <% disabled = "" %>
12 12 %if c.extern_type != 'rhodecode':
13 13 <% readonly = "readonly" %>
14 14 <% disabled = " disabled" %>
15 15 <div class="infoform">
16 16 <div class="fields">
17 17 <p>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</p>
18 18 </div>
19 19 </div>
20 20 %endif
21 21 <div class="form">
22 22 <div class="fields">
23 23 <div class="field">
24 24 <div class="label photo">
25 25 ${_('Photo')}:
26 26 </div>
27 27 <div class="input profile">
28 28 %if c.visual.use_gravatar:
29 29 ${base.gravatar(c.user.email, 100)}
30 30 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
31 31 %else:
32 32 ${base.gravatar(c.user.email, 20)}
33 33 ${_('Avatars are disabled')}
34 34 %endif
35 35 </div>
36 36 </div>
37 37 <div class="field">
38 38 <div class="label">
39 39 ${_('Username')}:
40 40 </div>
41 41 <div class="input">
42 42 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
43 43 </div>
44 44 </div>
45 45 <div class="field">
46 46 <div class="label">
47 47 <label for="name">${_('First Name')}:</label>
48 48 </div>
49 49 <div class="input">
50 50 ${h.text('firstname', class_="medium")}
51 51 </div>
52 52 </div>
53 53
54 54 <div class="field">
55 55 <div class="label">
56 56 <label for="lastname">${_('Last Name')}:</label>
57 57 </div>
58 58 <div class="input">
59 59 ${h.text('lastname', class_="medium")}
60 60 </div>
61 61 </div>
62 62
63 63 <div class="field">
64 64 <div class="label">
65 65 <label for="email">${_('Email')}:</label>
66 66 </div>
67 67 <div class="input">
68 68 ## we should be able to edit email !
69 69 ${h.text('email', class_="medium")}
70 70 </div>
71 71 </div>
72 72 <div class="field">
73 73 <div class="label">
74 74 ${_('New Password')}:
75 75 </div>
76 76 <div class="input">
77 77 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
78 78 </div>
79 79 </div>
80 80 <div class="field">
81 81 <div class="label">
82 82 ${_('New Password Confirmation')}:
83 83 </div>
84 84 <div class="input">
85 85 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
86 86 </div>
87 87 </div>
88 88 <div class="field">
89 89 <div class="label-text">
90 90 ${_('Active')}:
91 91 </div>
92 92 <div class="input user-checkbox">
93 93 ${h.checkbox('active',value=True)}
94 94 </div>
95 95 </div>
96 96 <div class="field">
97 97 <div class="label-text">
98 98 ${_('Super Admin')}:
99 99 </div>
100 100 <div class="input user-checkbox">
101 101 ${h.checkbox('admin',value=True)}
102 102 </div>
103 103 </div>
104 104 <div class="field">
105 105 <div class="label-text">
106 106 ${_('Source of Record')}:
107 107 </div>
108 108 <div class="input">
109 109 <p>${c.extern_type}</p>
110 110 ${h.hidden('extern_type', readonly="readonly")}
111 111 </div>
112 112 </div>
113 113 <div class="field">
114 114 <div class="label-text">
115 115 ${_('Name in Source of Record')}:
116 116 </div>
117 117 <div class="input">
118 118 <p>${c.extern_name}</p>
119 119 ${h.hidden('extern_name', readonly="readonly")}
120 120 </div>
121 121 </div>
122 122 <div class="field">
123 123 <div class="label">
124 124 ${_('Language')}:
125 125 </div>
126 126 <div class="input">
127 127 ## allowed_languages is defined in the users.py
128 128 ## c.language comes from base.py as a default language
129 129 ${h.select('language', c.language, c.allowed_languages)}
130 <p class="help-block">${h.literal(_('Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.url('rhodecode_translations'))})}</p>
130 <p class="help-block">${h.literal(_('Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
131 131 </div>
132 132 </div>
133 133 <div class="buttons">
134 134 ${h.submit('save', _('Save'), class_="btn")}
135 135 ${h.reset('reset', _('Reset'), class_="btn")}
136 136 </div>
137 137 </div>
138 138 </div>
139 139 ${h.end_form()}
140 140 </div>
141 141 </div>
142 142 </div>
143 143
144 144 <script>
145 145 $('#language').select2({
146 146 'containerCssClass': "drop-menu",
147 147 'dropdownCssClass': "drop-menu-dropdown",
148 148 'dropdownAutoWidth': true
149 149 });
150 150 </script>
@@ -1,601 +1,601 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
76 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${title}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.author_string(email)}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 236 <li class="${is_active('showpullrequest')}">
237 237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
241 241 <div class="menulabel">${_('Pull Requests')}</div>
242 242 </a>
243 243 </li>
244 244 %endif
245 245 <li class="${is_active('options')}">
246 246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 247 <ul class="submenu">
248 248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 249 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 250 %endif
251 251 %if c.rhodecode_db_repo.fork:
252 252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 253 ${_('Compare fork')}</a></li>
254 254 %endif
255 255
256 256 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
257 257
258 258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 259 %if c.rhodecode_db_repo.locked[0]:
260 260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 261 %else:
262 262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 263 %endif
264 264 %endif
265 265 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 269 %endif
270 270 %endif
271 271 </ul>
272 272 </li>
273 273 </ul>
274 274 </div>
275 275 <div class="clear"></div>
276 276 </div>
277 277 <!--- END CONTEXT BAR -->
278 278
279 279 </%def>
280 280
281 281 <%def name="usermenu(active=False)">
282 282 ## USER MENU
283 283 <li id="quick_login_li" class="${'active' if active else ''}">
284 284 <a id="quick_login_link" class="menulink childs">
285 285 ${gravatar(c.rhodecode_user.email, 20)}
286 286 <span class="user">
287 287 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 289 %else:
290 290 <span>${_('Sign in')}</span>
291 291 %endif
292 292 </span>
293 293 </a>
294 294
295 295 <div class="user-menu submenu">
296 296 <div id="quick_login">
297 297 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 298 <h4>${_('Sign in to your account')}</h4>
299 299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 300 <div class="form form-vertical">
301 301 <div class="fields">
302 302 <div class="field">
303 303 <div class="label">
304 304 <label for="username">${_('Username')}:</label>
305 305 </div>
306 306 <div class="input">
307 307 ${h.text('username',class_='focus',tabindex=1)}
308 308 </div>
309 309
310 310 </div>
311 311 <div class="field">
312 312 <div class="label">
313 313 <label for="password">${_('Password')}:</label>
314 314 %if h.HasPermissionAny('hg.password_reset.enabled')():
315 315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
316 316 %endif
317 317 </div>
318 318 <div class="input">
319 319 ${h.password('password',class_='focus',tabindex=2)}
320 320 </div>
321 321 </div>
322 322 <div class="buttons">
323 323 <div class="register">
324 324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
325 325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
326 326 %endif
327 327 </div>
328 328 <div class="submit">
329 329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
330 330 </div>
331 331 </div>
332 332 </div>
333 333 </div>
334 334 ${h.end_form()}
335 335 %else:
336 336 <div class="">
337 337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
338 338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
339 339 <div class="email">${c.rhodecode_user.email}</div>
340 340 </div>
341 341 <div class="">
342 342 <ol class="links">
343 343 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
344 344 % if c.rhodecode_user.personal_repo_group:
345 345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
346 346 % endif
347 347 <li class="logout">
348 348 ${h.secure_form(h.route_path('logout'))}
349 349 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
350 350 ${h.end_form()}
351 351 </li>
352 352 </ol>
353 353 </div>
354 354 %endif
355 355 </div>
356 356 </div>
357 357 %if c.rhodecode_user.username != h.DEFAULT_USER:
358 358 <div class="pill_container">
359 359 % if c.unread_notifications == 0:
360 360 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
361 361 % else:
362 362 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
363 363 % endif
364 364 </div>
365 365 % endif
366 366 </li>
367 367 </%def>
368 368
369 369 <%def name="menu_items(active=None)">
370 370 <%
371 371 def is_active(selected):
372 372 if selected == active:
373 373 return "active"
374 374 return ""
375 375 %>
376 376 <ul id="quick" class="main_nav navigation horizontal-list">
377 377 <!-- repo switcher -->
378 378 <li class="${is_active('repositories')} repo_switcher_li has_select2">
379 379 <input id="repo_switcher" name="repo_switcher" type="hidden">
380 380 </li>
381 381
382 382 ## ROOT MENU
383 383 %if c.rhodecode_user.username != h.DEFAULT_USER:
384 384 <li class="${is_active('journal')}">
385 385 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
386 386 <div class="menulabel">${_('Journal')}</div>
387 387 </a>
388 388 </li>
389 389 %else:
390 390 <li class="${is_active('journal')}">
391 391 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
392 392 <div class="menulabel">${_('Public journal')}</div>
393 393 </a>
394 394 </li>
395 395 %endif
396 396 <li class="${is_active('gists')}">
397 397 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
398 398 <div class="menulabel">${_('Gists')}</div>
399 399 </a>
400 400 </li>
401 401 <li class="${is_active('search')}">
402 402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
403 403 <div class="menulabel">${_('Search')}</div>
404 404 </a>
405 405 </li>
406 406 % if h.HasPermissionAll('hg.admin')('access admin main page'):
407 407 <li class="${is_active('admin')}">
408 408 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
409 409 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
410 410 </a>
411 411 ${admin_menu()}
412 412 </li>
413 413 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
414 414 <li class="${is_active('admin')}">
415 415 <a class="menulink childs" title="${_('Delegated Admin settings')}">
416 416 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 417 </a>
418 418 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
419 419 c.rhodecode_user.repository_groups_admin,
420 420 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
421 421 </li>
422 422 % endif
423 423 % if c.debug_style:
424 424 <li class="${is_active('debug_style')}">
425 425 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
426 426 <div class="menulabel">${_('Style')}</div>
427 427 </a>
428 428 </li>
429 429 % endif
430 430 ## render extra user menu
431 431 ${usermenu(active=(active=='my_account'))}
432 432 </ul>
433 433
434 434 <script type="text/javascript">
435 435 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
436 436
437 437 /*format the look of items in the list*/
438 438 var format = function(state, escapeMarkup){
439 439 if (!state.id){
440 440 return state.text; // optgroup
441 441 }
442 442 var obj_dict = state.obj;
443 443 var tmpl = '';
444 444
445 445 if(obj_dict && state.type == 'repo'){
446 446 if(obj_dict['repo_type'] === 'hg'){
447 447 tmpl += '<i class="icon-hg"></i> ';
448 448 }
449 449 else if(obj_dict['repo_type'] === 'git'){
450 450 tmpl += '<i class="icon-git"></i> ';
451 451 }
452 452 else if(obj_dict['repo_type'] === 'svn'){
453 453 tmpl += '<i class="icon-svn"></i> ';
454 454 }
455 455 if(obj_dict['private']){
456 456 tmpl += '<i class="icon-lock" ></i> ';
457 457 }
458 458 else if(visual_show_public_icon){
459 459 tmpl += '<i class="icon-unlock-alt"></i> ';
460 460 }
461 461 }
462 462 if(obj_dict && state.type == 'commit') {
463 463 tmpl += '<i class="icon-tag"></i>';
464 464 }
465 465 if(obj_dict && state.type == 'group'){
466 466 tmpl += '<i class="icon-folder-close"></i> ';
467 467 }
468 468 tmpl += escapeMarkup(state.text);
469 469 return tmpl;
470 470 };
471 471
472 472 var formatResult = function(result, container, query, escapeMarkup) {
473 473 return format(result, escapeMarkup);
474 474 };
475 475
476 476 var formatSelection = function(data, container, escapeMarkup) {
477 477 return format(data, escapeMarkup);
478 478 };
479 479
480 480 $("#repo_switcher").select2({
481 481 cachedDataSource: {},
482 482 minimumInputLength: 2,
483 483 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
484 484 dropdownAutoWidth: true,
485 485 formatResult: formatResult,
486 486 formatSelection: formatSelection,
487 487 containerCssClass: "repo-switcher",
488 488 dropdownCssClass: "repo-switcher-dropdown",
489 489 escapeMarkup: function(m){
490 490 // don't escape our custom placeholder
491 491 if(m.substr(0,23) == '<div class="menulabel">'){
492 492 return m;
493 493 }
494 494
495 495 return Select2.util.escapeMarkup(m);
496 496 },
497 497 query: $.debounce(250, function(query){
498 498 self = this;
499 499 var cacheKey = query.term;
500 500 var cachedData = self.cachedDataSource[cacheKey];
501 501
502 502 if (cachedData) {
503 503 query.callback({results: cachedData.results});
504 504 } else {
505 505 $.ajax({
506 506 url: pyroutes.url('goto_switcher_data'),
507 507 data: {'query': query.term},
508 508 dataType: 'json',
509 509 type: 'GET',
510 510 success: function(data) {
511 511 self.cachedDataSource[cacheKey] = data;
512 512 query.callback({results: data.results});
513 513 },
514 514 error: function(data, textStatus, errorThrown) {
515 515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
516 516 }
517 517 })
518 518 }
519 519 })
520 520 });
521 521
522 522 $("#repo_switcher").on('select2-selecting', function(e){
523 523 e.preventDefault();
524 524 window.location = e.choice.url;
525 525 });
526 526
527 527 </script>
528 528 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
529 529 </%def>
530 530
531 531 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
532 532 <div class="modal-dialog">
533 533 <div class="modal-content">
534 534 <div class="modal-header">
535 535 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
536 536 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
537 537 </div>
538 538 <div class="modal-body">
539 539 <div class="block-left">
540 540 <table class="keyboard-mappings">
541 541 <tbody>
542 542 <tr>
543 543 <th></th>
544 544 <th>${_('Site-wide shortcuts')}</th>
545 545 </tr>
546 546 <%
547 547 elems = [
548 548 ('/', 'Open quick search box'),
549 549 ('g h', 'Goto home page'),
550 550 ('g g', 'Goto my private gists page'),
551 551 ('g G', 'Goto my public gists page'),
552 552 ('n r', 'New repository page'),
553 553 ('n g', 'New gist page'),
554 554 ]
555 555 %>
556 556 %for key, desc in elems:
557 557 <tr>
558 558 <td class="keys">
559 559 <span class="key tag">${key}</span>
560 560 </td>
561 561 <td>${desc}</td>
562 562 </tr>
563 563 %endfor
564 564 </tbody>
565 565 </table>
566 566 </div>
567 567 <div class="block-left">
568 568 <table class="keyboard-mappings">
569 569 <tbody>
570 570 <tr>
571 571 <th></th>
572 572 <th>${_('Repositories')}</th>
573 573 </tr>
574 574 <%
575 575 elems = [
576 576 ('g s', 'Goto summary page'),
577 577 ('g c', 'Goto changelog page'),
578 578 ('g f', 'Goto files page'),
579 579 ('g F', 'Goto files page with file search activated'),
580 580 ('g p', 'Goto pull requests page'),
581 581 ('g o', 'Goto repository settings'),
582 582 ('g O', 'Goto repository permissions settings'),
583 583 ]
584 584 %>
585 585 %for key, desc in elems:
586 586 <tr>
587 587 <td class="keys">
588 588 <span class="key tag">${key}</span>
589 589 </td>
590 590 <td>${desc}</td>
591 591 </tr>
592 592 %endfor
593 593 </tbody>
594 594 </table>
595 595 </div>
596 596 </div>
597 597 <div class="modal-footer">
598 598 </div>
599 599 </div><!-- /.modal-content -->
600 600 </div><!-- /.modal-dialog -->
601 601 </div><!-- /.modal -->
@@ -1,218 +1,218 b''
1 1 ## snippet for displaying issue tracker settings
2 2 ## usage:
3 3 ## <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
4 4 ## ${its.issue_tracker_settings_table(patterns, form_url, delete_url)}
5 5 ## ${its.issue_tracker_settings_test(test_url)}
6 6
7 7 <%def name="issue_tracker_settings_table(patterns, form_url, delete_url)">
8 8 <table class="rctable issuetracker">
9 9 <tr>
10 10 <th>${_('Description')}</th>
11 11 <th>${_('Pattern')}</th>
12 12 <th>${_('Url')}</th>
13 13 <th>${_('Prefix')}</th>
14 14 <th ></th>
15 15 </tr>
16 16 <tr>
17 17 <td class="td-description issue-tracker-example">Example</td>
18 18 <td class="td-regex issue-tracker-example">${'(?:#)(?P<issue_id>\d+)'}</td>
19 19 <td class="td-url issue-tracker-example">${'https://myissueserver.com/${repo}/issue/${issue_id}'}</td>
20 20 <td class="td-prefix issue-tracker-example">#</td>
21 <td class="issue-tracker-example"><a href="https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html" target="_blank">${_('Read more')}</a></td>
21 <td class="issue-tracker-example"><a href="${h.route_url('enterprise_issue_tracker_settings')}" target="_blank">${_('Read more')}</a></td>
22 22 </tr>
23 23 %for uid, entry in patterns:
24 24 <tr id="entry_${uid}">
25 25 <td class="td-description issuetracker_desc">
26 26 <span class="entry">
27 27 ${entry.desc}
28 28 </span>
29 29 <span class="edit">
30 30 ${h.text('new_pattern_description_'+uid, class_='medium-inline', value=entry.desc or '')}
31 31 </span>
32 32 </td>
33 33 <td class="td-regex issuetracker_pat">
34 34 <span class="entry">
35 35 ${entry.pat}
36 36 </span>
37 37 <span class="edit">
38 38 ${h.text('new_pattern_pattern_'+uid, class_='medium-inline', value=entry.pat or '')}
39 39 </span>
40 40 </td>
41 41 <td class="td-url issuetracker_url">
42 42 <span class="entry">
43 43 ${entry.url}
44 44 </span>
45 45 <span class="edit">
46 46 ${h.text('new_pattern_url_'+uid, class_='medium-inline', value=entry.url or '')}
47 47 </span>
48 48 </td>
49 49 <td class="td-prefix issuetracker_pref">
50 50 <span class="entry">
51 51 ${entry.pref}
52 52 </span>
53 53 <span class="edit">
54 54 ${h.text('new_pattern_prefix_'+uid, class_='medium-inline', value=entry.pref or '')}
55 55 </span>
56 56 </td>
57 57 <td class="td-action">
58 58 <div class="grid_edit">
59 59 <span class="entry">
60 60 <a class="edit_issuetracker_entry" href="">${_('Edit')}</a>
61 61 </span>
62 62 <span class="edit">
63 63 ${h.hidden('uid', uid)}
64 64 </span>
65 65 </div>
66 66 <div class="grid_delete">
67 67 <span class="entry">
68 68 <a class="btn btn-link btn-danger delete_issuetracker_entry" data-desc="${entry.desc}" data-uid="${uid}">
69 69 ${_('Delete')}
70 70 </a>
71 71 </span>
72 72 <span class="edit">
73 73 <a class="btn btn-link btn-danger edit_issuetracker_cancel" data-uid="${uid}">${_('Cancel')}</a>
74 74 </span>
75 75 </div>
76 76 </td>
77 77 </tr>
78 78 %endfor
79 79 <tr id="last-row"></tr>
80 80 </table>
81 81 <p>
82 82 <a id="add_pattern" class="link">
83 83 ${_('Add new')}
84 84 </a>
85 85 </p>
86 86
87 87 <script type="text/javascript">
88 88 var newEntryLabel = $('label[for="new_entry"]');
89 89
90 90 var resetEntry = function() {
91 91 newEntryLabel.text("${_('New Entry')}:");
92 92 };
93 93
94 94 var delete_pattern = function(entry) {
95 95 if (confirm("${_('Confirm to remove this pattern:')} "+$(entry).data('desc'))) {
96 96 var request = $.ajax({
97 97 type: "POST",
98 98 url: "${delete_url}",
99 99 data: {
100 100 '_method': 'delete',
101 101 'csrf_token': CSRF_TOKEN,
102 102 'uid':$(entry).data('uid')
103 103 },
104 104 success: function(){
105 105 location.reload();
106 106 },
107 107 error: function(data, textStatus, errorThrown){
108 108 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
109 109 }
110 110 });
111 111 };
112 112 }
113 113
114 114 $('.delete_issuetracker_entry').on('click', function(e){
115 115 e.preventDefault();
116 116 delete_pattern(this);
117 117 });
118 118
119 119 $('.edit_issuetracker_entry').on('click', function(e){
120 120 e.preventDefault();
121 121 $(this).parents('tr').addClass('editopen');
122 122 });
123 123
124 124 $('.edit_issuetracker_cancel').on('click', function(e){
125 125 e.preventDefault();
126 126 $(this).parents('tr').removeClass('editopen');
127 127 // Reset to original value
128 128 var uid = $(this).data('uid');
129 129 $('#'+uid+' input').each(function(e) {
130 130 this.value = this.defaultValue;
131 131 });
132 132 });
133 133
134 134 $('input#reset').on('click', function(e) {
135 135 resetEntry();
136 136 });
137 137
138 138 $('#add_pattern').on('click', function(e) {
139 139 addNewPatternInput();
140 140 });
141 141 </script>
142 142 </%def>
143 143
144 144 <%def name="issue_tracker_new_row()">
145 145 <table id="add-row-tmpl" style="display: none;">
146 146 <tbody>
147 147 <tr class="new_pattern">
148 148 <td class="td-description issuetracker_desc">
149 149 <span class="entry">
150 150 <input class="medium-inline" id="description_##UUID##" name="new_pattern_description_##UUID##" value="##DESCRIPTION##" type="text">
151 151 </span>
152 152 </td>
153 153 <td class="td-regex issuetracker_pat">
154 154 <span class="entry">
155 155 <input class="medium-inline" id="pattern_##UUID##" name="new_pattern_pattern_##UUID##" placeholder="Pattern"
156 156 value="##PATTERN##" type="text">
157 157 </span>
158 158 </td>
159 159 <td class="td-url issuetracker_url">
160 160 <span class="entry">
161 161 <input class="medium-inline" id="url_##UUID##" name="new_pattern_url_##UUID##" placeholder="Url" value="##URL##" type="text">
162 162 </span>
163 163 </td>
164 164 <td class="td-prefix issuetracker_pref">
165 165 <span class="entry">
166 166 <input class="medium-inline" id="prefix_##UUID##" name="new_pattern_prefix_##UUID##" placeholder="Prefix" value="##PREFIX##" type="text">
167 167 </span>
168 168 </td>
169 169 <td class="td-action">
170 170 </td>
171 171 <input id="uid_##UUID##" name="uid_##UUID##" type="hidden" value="">
172 172 </tr>
173 173 </tbody>
174 174 </table>
175 175 </%def>
176 176
177 177 <%def name="issue_tracker_settings_test(test_url)">
178 178 <div class="form-vertical">
179 179 <div class="fields">
180 180 <div class="field">
181 181 <div class='textarea-full'>
182 182 <textarea id="test_pattern_data" >
183 183 This commit fixes ticket #451.
184 184 This is an example text for testing issue tracker patterns, add a pattern here and
185 185 hit preview to see the link
186 186 </textarea>
187 187 </div>
188 188 </div>
189 189 </div>
190 190 <div class="test_pattern_preview">
191 191 <div id="test_pattern" class="btn btn-small" >${_('Preview')}</div>
192 192 <p>${_('Test Pattern Preview')}</p>
193 193 <div id="test_pattern_result"></div>
194 194 </div>
195 195 </div>
196 196
197 197 <script type="text/javascript">
198 198 $('#test_pattern').on('click', function(e) {
199 199 $.ajax({
200 200 type: "POST",
201 201 url: "${test_url}",
202 202 data: {
203 203 'test_text': $('#test_pattern_data').val(),
204 204 'csrf_token': CSRF_TOKEN
205 205 },
206 206 success: function(data){
207 207 $('#test_pattern_result').html(data);
208 208 },
209 209 error: function(jqXHR, textStatus, errorThrown){
210 210 $('#test_pattern_result').html('Error: ' + errorThrown);
211 211 }
212 212 });
213 213 $('#test_pattern_result').show();
214 214 });
215 215 </script>
216 216 </%def>
217 217
218 218
@@ -1,322 +1,322 b''
1 1 ## snippet for displaying vcs settings
2 2 ## usage:
3 3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
4 4 ## ${vcss.vcs_settings_fields()}
5 5
6 6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, allow_repo_location_change=False, **kwargs)">
7 7 % if display_globals:
8 8 <div class="panel panel-default">
9 9 <div class="panel-heading" id="general">
10 10 <h3 class="panel-title">${_('General')}<a class="permalink" href="#general"> ΒΆ</a></h3>
11 11 </div>
12 12 <div class="panel-body">
13 13 <div class="field">
14 14 <div class="checkbox">
15 15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 17 </div>
18 18 <div class="label">
19 19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
20 20 </div>
21 21 </div>
22 22 </div>
23 23 </div>
24 24 % endif
25 25
26 26 % if display_globals:
27 27 <div class="panel panel-default">
28 28 <div class="panel-heading" id="vcs-storage-options">
29 29 <h3 class="panel-title">${_('Main Storage Location')}<a class="permalink" href="#vcs-storage-options"> ΒΆ</a></h3>
30 30 </div>
31 31 <div class="panel-body">
32 32 <div class="field">
33 33 <div class="inputx locked_input">
34 34 %if allow_repo_location_change:
35 35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
36 36 <span id="path_unlock" class="tooltip"
37 37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
38 38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
39 39 </span>
40 40 %else:
41 41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
42 42 ## form still requires this but we cannot internally change it anyway
43 43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
44 44 %endif
45 45 </div>
46 46 </div>
47 47 <div class="label">
48 48 <span class="help-block">${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')}</span>
49 49 </div>
50 50 </div>
51 51 </div>
52 52 % endif
53 53
54 54 % if display_globals or repo_type in ['git', 'hg']:
55 55 <div class="panel panel-default">
56 56 <div class="panel-heading" id="vcs-hooks-options">
57 57 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"> ΒΆ</a></h3>
58 58 </div>
59 59 <div class="panel-body">
60 60 <div class="field">
61 61 <div class="checkbox">
62 62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
63 63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
64 64 </div>
65 65
66 66 <div class="label">
67 67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
68 68 </div>
69 69 <div class="checkbox">
70 70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
71 71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
72 72 </div>
73 73 <div class="label">
74 74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
75 75 </div>
76 76 <div class="checkbox">
77 77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
78 78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
79 79 </div>
80 80 <div class="label">
81 81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
82 82 </div>
83 83 </div>
84 84 </div>
85 85 </div>
86 86 % endif
87 87
88 88 % if display_globals or repo_type in ['hg']:
89 89 <div class="panel panel-default">
90 90 <div class="panel-heading" id="vcs-hg-options">
91 91 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"> ΒΆ</a></h3>
92 92 </div>
93 93 <div class="panel-body">
94 94 <div class="checkbox">
95 95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
96 96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
97 97 </div>
98 98 <div class="label">
99 99 % if display_globals:
100 100 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
101 101 % else:
102 102 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
103 103 % endif
104 104 </div>
105 105
106 106 % if display_globals:
107 107 <div class="field">
108 108 <div class="input">
109 109 ${h.text('largefiles_usercache' + suffix, size=59)}
110 110 </div>
111 111 </div>
112 112 <div class="label">
113 113 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
114 114 </div>
115 115 % endif
116 116
117 117 <div class="checkbox">
118 118 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
119 119 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
120 120 </div>
121 121 <div class="label">
122 122 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
123 123 </div>
124 124 % if display_globals:
125 125 <div class="checkbox">
126 126 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
127 127 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
128 128 </div>
129 129 <div class="label">
130 130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
131 131 </div>
132 132 % endif
133 133 </div>
134 134 </div>
135 135 ## LABS for HG
136 136 % if c.labs_active:
137 137 <div class="panel panel-danger">
138 138 <div class="panel-heading">
139 139 <h3 class="panel-title">${_('Mercurial Labs Settings')} (${_('These features are considered experimental and may not work as expected.')})</h3>
140 140 </div>
141 141 <div class="panel-body">
142 142
143 143 <div class="checkbox">
144 144 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
145 145 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
146 146 </div>
147 147 <div class="label">
148 148 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
149 149 </div>
150 150
151 151 </div>
152 152 </div>
153 153 % endif
154 154
155 155 % endif
156 156
157 157 % if display_globals or repo_type in ['git']:
158 158 <div class="panel panel-default">
159 159 <div class="panel-heading" id="vcs-git-options">
160 160 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"> ΒΆ</a></h3>
161 161 </div>
162 162 <div class="panel-body">
163 163 <div class="checkbox">
164 164 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
165 165 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
166 166 </div>
167 167 <div class="label">
168 168 % if display_globals:
169 169 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
170 170 % else:
171 171 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
172 172 % endif
173 173 </div>
174 174
175 175 % if display_globals:
176 176 <div class="field">
177 177 <div class="input">
178 178 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
179 179 </div>
180 180 </div>
181 181 <div class="label">
182 182 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
183 183 </div>
184 184 % endif
185 185 </div>
186 186 </div>
187 187 % endif
188 188
189 189
190 190 % if display_globals:
191 191 <div class="panel panel-default">
192 192 <div class="panel-heading" id="vcs-global-svn-options">
193 193 <h3 class="panel-title">${_('Global Subversion Settings')}<a class="permalink" href="#vcs-global-svn-options"> ΒΆ</a></h3>
194 194 </div>
195 195 <div class="panel-body">
196 196 <div class="field">
197 197 <div class="checkbox">
198 198 ${h.checkbox('vcs_svn_proxy_http_requests_enabled' + suffix, 'True', **kwargs)}
199 199 <label for="vcs_svn_proxy_http_requests_enabled${suffix}">${_('Proxy subversion HTTP requests')}</label>
200 200 </div>
201 201 <div class="label">
202 202 <span class="help-block">
203 203 ${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}
204 <a href="${h.url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
204 <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
205 205 </span>
206 206 </div>
207 207 </div>
208 208 <div class="field">
209 209 <div class="label">
210 210 <label for="vcs_svn_proxy_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
211 211 </div>
212 212 <div class="input">
213 213 ${h.text('vcs_svn_proxy_http_server_url',size=59)}
214 214 % if c.svn_proxy_generate_config:
215 215 <span class="buttons">
216 216 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Generate Apache Config')}</button>
217 217 </span>
218 218 % endif
219 219 </div>
220 220 </div>
221 221 </div>
222 222 </div>
223 223 % endif
224 224
225 225 % if display_globals or repo_type in ['svn']:
226 226 <div class="panel panel-default">
227 227 <div class="panel-heading" id="vcs-svn-options">
228 228 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"> ΒΆ</a></h3>
229 229 </div>
230 230 <div class="panel-body">
231 231 <div class="field">
232 232 <div class="content" >
233 233 <label>${_('Repository patterns')}</label><br/>
234 234 </div>
235 235 </div>
236 236 <div class="label">
237 237 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
238 238 </div>
239 239
240 240 <div class="field branch_patterns">
241 241 <div class="input" >
242 242 <label>${_('Branches')}:</label><br/>
243 243 </div>
244 244 % if svn_branch_patterns:
245 245 % for branch in svn_branch_patterns:
246 246 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
247 247 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
248 248 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
249 249 % if kwargs.get('disabled') != 'disabled':
250 250 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
251 251 ${_('Delete')}
252 252 </span>
253 253 % endif
254 254 </div>
255 255 % endfor
256 256 %endif
257 257 </div>
258 258 % if kwargs.get('disabled') != 'disabled':
259 259 <div class="field branch_patterns">
260 260 <div class="input" >
261 261 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
262 262 </div>
263 263 </div>
264 264 % endif
265 265 <div class="field tag_patterns">
266 266 <div class="input" >
267 267 <label>${_('Tags')}:</label><br/>
268 268 </div>
269 269 % if svn_tag_patterns:
270 270 % for tag in svn_tag_patterns:
271 271 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
272 272 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
273 273 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
274 274 % if kwargs.get('disabled') != 'disabled':
275 275 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
276 276 ${_('Delete')}
277 277 </span>
278 278 %endif
279 279 </div>
280 280 % endfor
281 281 % endif
282 282 </div>
283 283 % if kwargs.get('disabled') != 'disabled':
284 284 <div class="field tag_patterns">
285 285 <div class="input" >
286 286 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
287 287 </div>
288 288 </div>
289 289 %endif
290 290 </div>
291 291 </div>
292 292 % else:
293 293 ${h.hidden('new_svn_branch' + suffix, '')}
294 294 ${h.hidden('new_svn_tag' + suffix, '')}
295 295 % endif
296 296
297 297
298 298 % if display_globals or repo_type in ['hg', 'git']:
299 299 <div class="panel panel-default">
300 300 <div class="panel-heading" id="vcs-pull-requests-options">
301 301 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
302 302 </div>
303 303 <div class="panel-body">
304 304 <div class="checkbox">
305 305 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
306 306 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
307 307 </div>
308 308 <div class="label">
309 309 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
310 310 </div>
311 311 <div class="checkbox">
312 312 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
313 313 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
314 314 </div>
315 315 <div class="label">
316 316 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
317 317 </div>
318 318 </div>
319 319 </div>
320 320 % endif
321 321
322 322 </%def>
@@ -1,405 +1,405 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ## usage:
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 4 ## ${comment.comment_block(comment)}
5 5 ##
6 6 <%namespace name="base" file="/base/base.mako"/>
7 7
8 8 <%def name="comment_block(comment, inline=False)">
9 9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 10 % if inline:
11 11 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
12 12 % else:
13 13 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
14 14 % endif
15 15
16 16
17 17 <div class="comment
18 18 ${'comment-inline' if inline else 'comment-general'}
19 19 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
20 20 id="comment-${comment.comment_id}"
21 21 line="${comment.line_no}"
22 22 data-comment-id="${comment.comment_id}"
23 23 data-comment-type="${comment.comment_type}"
24 24 data-comment-inline=${h.json.dumps(inline)}
25 25 style="${'display: none;' if outdated_at_ver else ''}">
26 26
27 27 <div class="meta">
28 28 <div class="comment-type-label">
29 29 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
30 30 % if comment.comment_type == 'todo':
31 31 % if comment.resolved:
32 32 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
33 33 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
34 34 </div>
35 35 % else:
36 36 <div class="resolved tooltip" style="display: none">
37 37 <span>${comment.comment_type}</span>
38 38 </div>
39 39 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
40 40 ${comment.comment_type}
41 41 </div>
42 42 % endif
43 43 % else:
44 44 % if comment.resolved_comment:
45 45 fix
46 46 % else:
47 47 ${comment.comment_type or 'note'}
48 48 % endif
49 49 % endif
50 50 </div>
51 51 </div>
52 52
53 53 <div class="author ${'author-inline' if inline else 'author-general'}">
54 54 ${base.gravatar_with_user(comment.author.email, 16)}
55 55 </div>
56 56 <div class="date">
57 57 ${h.age_component(comment.modified_at, time_is_local=True)}
58 58 </div>
59 59 % if inline:
60 60 <span></span>
61 61 % else:
62 62 <div class="status-change">
63 63 % if comment.pull_request:
64 64 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
65 65 % if comment.status_change:
66 66 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
67 67 % else:
68 68 ${_('pull request #%s') % comment.pull_request.pull_request_id}
69 69 % endif
70 70 </a>
71 71 % else:
72 72 % if comment.status_change:
73 73 ${_('Status change on commit')}:
74 74 % endif
75 75 % endif
76 76 </div>
77 77 % endif
78 78
79 79 % if comment.status_change:
80 80 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
81 81 <div title="${_('Commit status')}" class="changeset-status-lbl">
82 82 ${comment.status_change[0].status_lbl}
83 83 </div>
84 84 % endif
85 85
86 86 % if comment.resolved_comment:
87 87 <a class="has-spacer-before" href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
88 88 ${_('resolves comment #{}').format(comment.resolved_comment.comment_id)}
89 89 </a>
90 90 % endif
91 91
92 92 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
93 93
94 94 <div class="comment-links-block">
95 95 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
96 96 <span class="tag authortag tooltip" title="${_('Pull request author')}">
97 97 ${_('author')}
98 98 </span>
99 99 |
100 100 % endif
101 101 % if inline:
102 102 <div class="pr-version-inline">
103 103 <a href="${h.url.current(version=comment.pull_request_version_id, anchor='comment-{}'.format(comment.comment_id))}">
104 104 % if outdated_at_ver:
105 105 <code class="pr-version-num" title="${_('Outdated comment from pull request version {0}').format(pr_index_ver)}">
106 106 outdated ${'v{}'.format(pr_index_ver)} |
107 107 </code>
108 108 % elif pr_index_ver:
109 109 <code class="pr-version-num" title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
110 110 ${'v{}'.format(pr_index_ver)} |
111 111 </code>
112 112 % endif
113 113 </a>
114 114 </div>
115 115 % else:
116 116 % if comment.pull_request_version_id and pr_index_ver:
117 117 |
118 118 <div class="pr-version">
119 119 % if comment.outdated:
120 120 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
121 121 ${_('Outdated comment from pull request version {}').format(pr_index_ver)}
122 122 </a>
123 123 % else:
124 124 <div title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
125 125 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
126 126 <code class="pr-version-num">
127 127 ${'v{}'.format(pr_index_ver)}
128 128 </code>
129 129 </a>
130 130 </div>
131 131 % endif
132 132 </div>
133 133 % endif
134 134 % endif
135 135
136 136 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
137 137 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
138 138 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
139 139 ## permissions to delete
140 140 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
141 141 ## TODO: dan: add edit comment here
142 142 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
143 143 %else:
144 144 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
145 145 %endif
146 146 %else:
147 147 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
148 148 %endif
149 149
150 150 % if outdated_at_ver:
151 151 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
152 152 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
153 153 % else:
154 154 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
155 155 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
156 156 % endif
157 157
158 158 </div>
159 159 </div>
160 160 <div class="text">
161 161 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
162 162 </div>
163 163
164 164 </div>
165 165 </%def>
166 166
167 167 ## generate main comments
168 168 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
169 169 <div class="general-comments" id="comments">
170 170 %for comment in comments:
171 171 <div id="comment-tr-${comment.comment_id}">
172 172 ## only render comments that are not from pull request, or from
173 173 ## pull request and a status change
174 174 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
175 175 ${comment_block(comment)}
176 176 %endif
177 177 </div>
178 178 %endfor
179 179 ## to anchor ajax comments
180 180 <div id="injected_page_comments"></div>
181 181 </div>
182 182 </%def>
183 183
184 184
185 185 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
186 186
187 187 <div class="comments">
188 188 <%
189 189 if is_pull_request:
190 190 placeholder = _('Leave a comment on this Pull Request.')
191 191 elif is_compare:
192 192 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
193 193 else:
194 194 placeholder = _('Leave a comment on this Commit.')
195 195 %>
196 196
197 197 % if c.rhodecode_user.username != h.DEFAULT_USER:
198 198 <div class="js-template" id="cb-comment-general-form-template">
199 199 ## template generated for injection
200 200 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
201 201 </div>
202 202
203 203 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
204 204 ## inject form here
205 205 </div>
206 206 <script type="text/javascript">
207 207 var lineNo = 'general';
208 208 var resolvesCommentId = null;
209 209 var generalCommentForm = Rhodecode.comments.createGeneralComment(
210 210 lineNo, "${placeholder}", resolvesCommentId);
211 211
212 212 // set custom success callback on rangeCommit
213 213 % if is_compare:
214 214 generalCommentForm.setHandleFormSubmit(function(o) {
215 215 var self = generalCommentForm;
216 216
217 217 var text = self.cm.getValue();
218 218 var status = self.getCommentStatus();
219 219 var commentType = self.getCommentType();
220 220
221 221 if (text === "" && !status) {
222 222 return;
223 223 }
224 224
225 225 // we can pick which commits we want to make the comment by
226 226 // selecting them via click on preview pane, this will alter the hidden inputs
227 227 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
228 228
229 229 var commitIds = [];
230 230 $('#changeset_compare_view_content .compare_select').each(function(el) {
231 231 var commitId = this.id.replace('row-', '');
232 232 if ($(this).hasClass('hl') || !cherryPicked) {
233 233 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
234 234 commitIds.push(commitId);
235 235 } else {
236 236 $("input[data-commit-id='{0}']".format(commitId)).val('')
237 237 }
238 238 });
239 239
240 240 self.setActionButtonsDisabled(true);
241 241 self.cm.setOption("readOnly", true);
242 242 var postData = {
243 243 'text': text,
244 244 'changeset_status': status,
245 245 'comment_type': commentType,
246 246 'commit_ids': commitIds,
247 247 'csrf_token': CSRF_TOKEN
248 248 };
249 249
250 250 var submitSuccessCallback = function(o) {
251 251 location.reload(true);
252 252 };
253 253 var submitFailCallback = function(){
254 254 self.resetCommentFormState(text)
255 255 };
256 256 self.submitAjaxPOST(
257 257 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
258 258 });
259 259 % endif
260 260
261 261
262 262 </script>
263 263 % else:
264 264 ## form state when not logged in
265 265 <div class="comment-form ac">
266 266
267 267 <div class="comment-area">
268 268 <div class="comment-area-header">
269 269 <ul class="nav-links clearfix">
270 270 <li class="active">
271 271 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
272 272 </li>
273 273 <li class="">
274 274 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
275 275 </li>
276 276 </ul>
277 277 </div>
278 278
279 279 <div class="comment-area-write" style="display: block;">
280 280 <div id="edit-container">
281 281 <div style="padding: 40px 0">
282 282 ${_('You need to be logged in to leave comments.')}
283 283 <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
284 284 </div>
285 285 </div>
286 286 <div id="preview-container" class="clearfix" style="display: none;">
287 287 <div id="preview-box" class="preview-box"></div>
288 288 </div>
289 289 </div>
290 290
291 291 <div class="comment-area-footer">
292 292 <div class="toolbar">
293 293 <div class="toolbar-text">
294 294 </div>
295 295 </div>
296 296 </div>
297 297 </div>
298 298
299 299 <div class="comment-footer">
300 300 </div>
301 301
302 302 </div>
303 303 % endif
304 304
305 305 <script type="text/javascript">
306 306 bindToggleButtons();
307 307 </script>
308 308 </div>
309 309 </%def>
310 310
311 311
312 312 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
313 313 ## comment injected based on assumption that user is logged in
314 314
315 315 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
316 316
317 317 <div class="comment-area">
318 318 <div class="comment-area-header">
319 319 <ul class="nav-links clearfix">
320 320 <li class="active">
321 321 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
322 322 </li>
323 323 <li class="">
324 324 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
325 325 </li>
326 326 <li class="pull-right">
327 327 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
328 328 % for val in c.visual.comment_types:
329 329 <option value="${val}">${val.upper()}</option>
330 330 % endfor
331 331 </select>
332 332 </li>
333 333 </ul>
334 334 </div>
335 335
336 336 <div class="comment-area-write" style="display: block;">
337 337 <div id="edit-container_${lineno_id}">
338 338 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
339 339 </div>
340 340 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
341 341 <div id="preview-box_${lineno_id}" class="preview-box"></div>
342 342 </div>
343 343 </div>
344 344
345 345 <div class="comment-area-footer">
346 346 <div class="toolbar">
347 347 <div class="toolbar-text">
348 348 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
349 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
349 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
350 350 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
351 351 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
352 352 )
353 353 )|n}
354 354 </div>
355 355 </div>
356 356 </div>
357 357 </div>
358 358
359 359 <div class="comment-footer">
360 360
361 361 % if review_statuses:
362 362 <div class="status_box">
363 363 <select id="change_status_${lineno_id}" name="changeset_status">
364 364 <option></option> ## Placeholder
365 365 % for status, lbl in review_statuses:
366 366 <option value="${status}" data-status="${status}">${lbl}</option>
367 367 %if is_pull_request and change_status and status in ('approved', 'rejected'):
368 368 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
369 369 %endif
370 370 % endfor
371 371 </select>
372 372 </div>
373 373 % endif
374 374
375 375 ## inject extra inputs into the form
376 376 % if form_extras and isinstance(form_extras, (list, tuple)):
377 377 <div id="comment_form_extras">
378 378 % for form_ex_el in form_extras:
379 379 ${form_ex_el|n}
380 380 % endfor
381 381 </div>
382 382 % endif
383 383
384 384 <div class="action-buttons">
385 385 ## inline for has a file, and line-number together with cancel hide button.
386 386 % if form_type == 'inline':
387 387 <input type="hidden" name="f_path" value="{0}">
388 388 <input type="hidden" name="line" value="${lineno_id}">
389 389 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
390 390 ${_('Cancel')}
391 391 </button>
392 392 % endif
393 393
394 394 % if form_type != 'inline':
395 395 <div class="action-buttons-extra"></div>
396 396 % endif
397 397
398 398 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
399 399
400 400 </div>
401 401 </div>
402 402
403 403 </form>
404 404
405 405 </%def> No newline at end of file
@@ -1,77 +1,77 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html>
3 3 <html xmlns="http://www.w3.org/1999/xhtml">
4 4 <head>
5 5 <title>Error - ${c.error_message}</title>
6 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 7 <meta name="robots" content="index, nofollow"/>
8 8 <link rel="icon" href="${h.asset('images/favicon.ico')}" sizes="16x16 32x32" type="image/png" />
9 9
10 10 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
11 11 %if c.redirect_time:
12 12 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
13 13 %endif
14 14
15 15 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
16 16 <!--[if IE]>
17 17 <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css')}" media="screen"/>
18 18 <![endif]-->
19 19 <style>body { background:#eeeeee; }</style>
20 20 <script type="text/javascript">
21 21 // register templateContext to pass template variables to JS
22 22 var templateContext = {timeago: {}};
23 23 </script>
24 24 <script type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
25 25 </head>
26 26 <body>
27 27 <% messages = h.flash.pop_messages() %>
28 28
29 29 <div class="wrapper error_page">
30 30 <div class="sidebar">
31 31 <a href="${h.url('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
32 32 </div>
33 33 <div class="main-content">
34 34 <h1>
35 35 <span class="error-branding">
36 36 ${h.branding(c.rhodecode_name)}
37 37 </span><br/>
38 38 ${c.error_message} | <span class="error_message">${c.error_explanation}</span>
39 39 </h1>
40 40 % if messages:
41 41 % for message in messages:
42 42 <div class="alert alert-${message.category}">${message}</div>
43 43 % endfor
44 44 % endif
45 45 %if c.redirect_time:
46 46 <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
47 47 %endif
48 48 <div class="inner-column">
49 49 <h4>Possible Causes</h4>
50 50 <ul>
51 51 % if c.causes:
52 52 %for cause in c.causes:
53 53 <li>${cause}</li>
54 54 %endfor
55 55 %else:
56 56 <li>The resource may have been deleted.</li>
57 57 <li>You may not have access to this repository.</li>
58 58 <li>The link may be incorrect.</li>
59 59 %endif
60 60 </ul>
61 61 </div>
62 62 <div class="inner-column">
63 63 <h4>Support</h4>
64 64 <p>For support, go to <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>.
65 It may be useful to include your log file; see the log file locations <a href="${h.url('enterprise_log_file_locations')}">here</a>.
65 It may be useful to include your log file; see the log file locations <a href="${h.route_url('enterprise_log_file_locations')}">here</a>.
66 66 </p>
67 67 </div>
68 68 <div class="inner-column">
69 69 <h4>Documentation</h4>
70 <p>For more information, see <a href="${h.url('enterprise_docs')}">docs.rhodecode.com</a>.</p>
70 <p>For more information, see <a href="${h.route_url('enterprise_docs')}">docs.rhodecode.com</a>.</p>
71 71 </div>
72 72 </div>
73 73 </div>
74 74
75 75 </body>
76 76
77 77 </html>
@@ -1,202 +1,202 b''
1 1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 2 <span class="branchtag tag">
3 3 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
4 4 <i class="icon-branch"></i>${ungettext(
5 5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 6 </span>
7 7
8 8 %if closed_branches:
9 9 <span class="branchtag tag">
10 10 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
11 11 <i class="icon-branch"></i>${ungettext(
12 12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 13 </span>
14 14 %endif
15 15
16 16 <span class="tagtag tag">
17 17 <a href="${h.url('tags_home',repo_name=c.repo_name)}" class="childs">
18 18 <i class="icon-tag"></i>${ungettext(
19 19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 20 </span>
21 21
22 22 %if bookmarks:
23 23 <span class="booktag tag">
24 24 <a href="${h.url('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 25 <i class="icon-bookmark"></i>${ungettext(
26 26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 27 </span>
28 28 %endif
29 29 </%def>
30 30
31 31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33 33
34 34 <div id="summary-menu-stats" class="summary-detail">
35 35 <div class="summary-detail-header">
36 36 <div class="breadcrumbs files_location">
37 37 <h4>
38 38 ${breadcrumbs_links}
39 39 </h4>
40 40 </div>
41 41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 42 ${_('Show More')}
43 43 </div>
44 44 </div>
45 45
46 46 <div class="fieldset">
47 47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
48 48 <div class="left-label disabled">
49 49 ${_('Read-only url')}:
50 50 </div>
51 51 <div class="right-content disabled">
52 52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
53 53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
54 54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
55 55 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
57 57 </div>
58 58 %else:
59 59 <div class="left-label">
60 60 ${_('Clone url')}:
61 61 </div>
62 62 <div class="right-content">
63 63 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
64 64 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
65 65 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
66 66 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
67 67 </div>
68 68 %endif
69 69 </div>
70 70
71 71 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
72 72 <div class="left-label">
73 73 ${_('Description')}:
74 74 </div>
75 75 <div class="right-content">
76 76 %if c.visual.stylify_metatags:
77 77 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
78 78 %else:
79 79 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
80 80 %endif
81 81 </div>
82 82 </div>
83 83
84 84 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
85 85 <div class="left-label">
86 86 ${_('Information')}:
87 87 </div>
88 88 <div class="right-content">
89 89
90 90 <div class="repo-size">
91 91 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
92 92
93 93 ## commits
94 94 % if commit_rev == -1:
95 95 ${ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
96 96 % else:
97 97 <a href="${h.url('changelog_home', repo_name=c.repo_name)}">
98 98 ${ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
99 99 % endif
100 100
101 101 ## forks
102 102 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
103 103 ${c.repository_forks} ${ungettext('Fork', 'Forks', c.repository_forks)}</a>,
104 104
105 105 ## repo size
106 106 % if commit_rev == -1:
107 107 <span class="stats-bullet">0 B</span>
108 108 % else:
109 109 <span class="stats-bullet" id="repo_size_container">
110 110 ${_('Calculating Repository Size...')}
111 111 </span>
112 112 % endif
113 113 </div>
114 114
115 115 <div class="commit-info">
116 116 <div class="tags">
117 117 % if c.rhodecode_repo:
118 118 ${refs_counters(
119 119 c.rhodecode_repo.branches,
120 120 c.rhodecode_repo.branches_closed,
121 121 c.rhodecode_repo.tags,
122 122 c.rhodecode_repo.bookmarks)}
123 123 % else:
124 124 ## missing requirements can make c.rhodecode_repo None
125 125 ${refs_counters([], [], [], [])}
126 126 % endif
127 127 </div>
128 128 </div>
129 129
130 130 </div>
131 131 </div>
132 132
133 133 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
134 134 <div class="left-label">
135 135 ${_('Statistics')}:
136 136 </div>
137 137 <div class="right-content">
138 138 <div class="input ${summary(c.show_stats)} statistics">
139 139 % if c.show_stats:
140 140 <div id="lang_stats" class="enabled">
141 141 ${_('Calculating Code Statistics...')}
142 142 </div>
143 143 % else:
144 144 <span class="disabled">
145 145 ${_('Statistics are disabled for this repository')}
146 146 </span>
147 147 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
148 148 , ${h.link_to(_('enable statistics'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
149 149 % endif
150 150 % endif
151 151 </div>
152 152
153 153 </div>
154 154 </div>
155 155
156 156 % if show_downloads:
157 157 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
158 158 <div class="left-label">
159 159 ${_('Downloads')}:
160 160 </div>
161 161 <div class="right-content">
162 162 <div class="input ${summary(c.show_stats)} downloads">
163 163 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
164 164 <span class="disabled">
165 165 ${_('There are no downloads yet')}
166 166 </span>
167 167 % elif not c.enable_downloads:
168 168 <span class="disabled">
169 169 ${_('Downloads are disabled for this repository')}
170 170 </span>
171 171 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
172 172 , ${h.link_to(_('enable downloads'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
173 173 % endif
174 174 % else:
175 175 <span class="enabled">
176 176 <a id="archive_link" class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
177 177 <i class="icon-archive"></i> tip.zip
178 178 ## replaced by some JS on select
179 179 </a>
180 180 </span>
181 181 ${h.hidden('download_options')}
182 182 % endif
183 183 </div>
184 184 </div>
185 185 </div>
186 186 % endif
187 187
188 188 </div><!--end summary-detail-->
189 189 </%def>
190 190
191 191 <%def name="summary_stats(gravatar_function)">
192 192 <div class="sidebar-right">
193 193 <div class="summary-detail-header">
194 194 <h4 class="item">
195 195 ${_('Owner')}
196 196 </h4>
197 197 </div>
198 198 <div class="sidebar-right-content">
199 199 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
200 200 </div>
201 201 </div><!--end sidebar-right-->
202 202 </%def>
@@ -1,40 +1,45 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import mock
22
21 import pytest
22 import requests
23 23 from rhodecode.config import routing_links
24 24
25 25
26 def test_connect_redirection_links():
27 link_config = [
28 {"name": "example_link",
29 "external_target": "http://example.com",
30 "target": "https://rhodecode.com/r/v1/enterprise/example",
31 },
32 ]
26 def check_connection():
27 try:
28 response = requests.get('https://rhodecode.com')
29 return response.status_code == 200
30 except Exception as e:
31 print(e)
32
33 return False
34
33 35
34 rmap = mock.Mock()
35 with mock.patch.object(routing_links, 'link_config', link_config):
36 routing_links.connect_redirection_links(rmap)
36 connection_available = pytest.mark.skipif(
37 not check_connection(), reason="No outside internet connection available")
38
37 39
38 rmap.connect.assert_called_with(
39 link_config[0]['name'], link_config[0]['target'],
40 _static=True)
40 @connection_available
41 def test_connect_redirection_links():
42
43 for link_data in routing_links.link_config:
44 response = requests.get(link_data['target'])
45 assert response.url == link_data['external_target']
General Comments 0
You need to be logged in to leave comments. Login now