##// END OF EJS Templates
observers: hide it for CE edition....
marcink -
r4516:6a883584 stable
parent child Browse files
Show More
@@ -1,111 +1,111 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from rhodecode.lib import helpers as h, rc_cache
22 22 from rhodecode.lib.utils2 import safe_int
23 23 from rhodecode.model.pull_request import get_diff_info
24 24 from rhodecode.model.db import PullRequestReviewers
25 25 # V3 - Reviewers, with default rules data
26 26 # v4 - Added observers metadata
27 27 REVIEWER_API_VERSION = 'V4'
28 28
29 29
30 30 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
31 31 """
32 32 Returns json struct of a reviewer for frontend
33 33
34 34 :param user: the reviewer
35 35 :param reasons: list of strings of why they are reviewers
36 36 :param mandatory: bool, to set user as mandatory
37 37 """
38 38 role = role or PullRequestReviewers.ROLE_REVIEWER
39 39 if role not in PullRequestReviewers.ROLES:
40 40 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
41 41
42 42 return {
43 43 'user_id': user.user_id,
44 44 'reasons': reasons or [],
45 45 'rules': rules or [],
46 46 'role': role,
47 47 'mandatory': mandatory,
48 48 'user_group': user_group,
49 49 'username': user.username,
50 50 'first_name': user.first_name,
51 51 'last_name': user.last_name,
52 52 'user_link': h.link_to_user(user),
53 53 'gravatar_link': h.gravatar_url(user.email, 14),
54 54 }
55 55
56 56
57 57 def to_reviewers(e):
58 58 if isinstance(e, (tuple, list)):
59 59 return map(reviewer_as_json, e)
60 60 else:
61 61 return reviewer_as_json(e)
62 62
63 63
64 64 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
65 65 include_diff_info=True):
66 66 """
67 67 Return json for default reviewers of a repository
68 68 """
69 69
70 70 diff_info = {}
71 71 if include_diff_info:
72 72 diff_info = get_diff_info(
73 73 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
74 74
75 75 reasons = ['Default reviewer', 'Repository owner']
76 76 json_reviewers = [reviewer_as_json(
77 77 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
78 78
79 79 compute_key = rc_cache.utils.compute_key_from_params(
80 80 current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
81 81 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
82 82 target_ref.commit_id)
83 83
84 84 return {
85 85 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
86 86 'compute_key': compute_key,
87 87 'diff_info': diff_info,
88 88 'reviewers': json_reviewers,
89 89 'rules': {},
90 90 'rules_data': {},
91 91 }
92 92
93 93
94 94 def validate_default_reviewers(review_members, reviewer_rules):
95 95 """
96 96 Function to validate submitted reviewers against the saved rules
97 97 """
98 98 reviewers = []
99 99 reviewer_by_id = {}
100 100 for r in review_members:
101 101 reviewer_user_id = safe_int(r['user_id'])
102 102 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
103 103
104 104 reviewer_by_id[reviewer_user_id] = entry
105 105 reviewers.append(entry)
106 106
107 107 return reviewers
108 108
109 109
110 def validate_observers(observer_members):
110 def validate_observers(observer_members, reviewer_rules):
111 111 return {}
@@ -1,767 +1,768 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import sys
23 23 import logging
24 24 import collections
25 25 import tempfile
26 26 import time
27 27
28 28 from paste.gzipper import make_gzip_middleware
29 29 import pyramid.events
30 30 from pyramid.wsgi import wsgiapp
31 31 from pyramid.authorization import ACLAuthorizationPolicy
32 32 from pyramid.config import Configurator
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 36 from pyramid.renderers import render_to_response
37 37
38 38 from rhodecode.model import meta
39 39 from rhodecode.config import patches
40 40 from rhodecode.config import utils as config_utils
41 41 from rhodecode.config.environment import load_pyramid_environment
42 42
43 43 import rhodecode.events
44 44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 45 from rhodecode.lib.request import Request
46 46 from rhodecode.lib.vcs import VCSCommunicationError
47 47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 50 from rhodecode.lib.celerylib.loader import configure_celery
51 51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 53 from rhodecode.lib.exc_tracking import store_exception
54 54 from rhodecode.subscribers import (
55 55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 56 write_metadata_if_needed, write_usage_data, inject_app_settings)
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 def is_http_error(response):
63 63 # error which should have traceback
64 64 return response.status_code > 499
65 65
66 66
67 67 def should_load_all():
68 68 """
69 69 Returns if all application components should be loaded. In some cases it's
70 70 desired to skip apps loading for faster shell script execution
71 71 """
72 72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 73 if ssh_cmd:
74 74 return False
75 75
76 76 return True
77 77
78 78
79 79 def make_pyramid_app(global_config, **settings):
80 80 """
81 81 Constructs the WSGI application based on Pyramid.
82 82
83 83 Specials:
84 84
85 85 * The application can also be integrated like a plugin via the call to
86 86 `includeme`. This is accompanied with the other utility functions which
87 87 are called. Changing this should be done with great care to not break
88 88 cases when these fragments are assembled from another place.
89 89
90 90 """
91 91
92 92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
93 93 # will be replaced by the value of the environment variable "NAME" in this case.
94 94 start_time = time.time()
95 95
96 96 debug = asbool(global_config.get('debug'))
97 97 if debug:
98 98 enable_debug()
99 99
100 100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101 101
102 102 global_config = _substitute_values(global_config, environ)
103 103 settings = _substitute_values(settings, environ)
104 104
105 105 sanitize_settings_and_apply_defaults(global_config, settings)
106 106
107 107 config = Configurator(settings=settings)
108 108
109 109 # Apply compatibility patches
110 110 patches.inspect_getargspec()
111 111
112 112 load_pyramid_environment(global_config, settings)
113 113
114 114 # Static file view comes first
115 115 includeme_first(config)
116 116
117 117 includeme(config)
118 118
119 119 pyramid_app = config.make_wsgi_app()
120 120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 121 pyramid_app.config = config
122 122
123 123 config.configure_celery(global_config['__file__'])
124 124 # creating the app uses a connection - return it after we are done
125 125 meta.Session.remove()
126 126 total_time = time.time() - start_time
127 127 log.info('Pyramid app `%s` created and configured in %.2fs',
128 128 pyramid_app.func_name, total_time)
129 129
130 130 return pyramid_app
131 131
132 132
133 133 def not_found_view(request):
134 134 """
135 135 This creates the view which should be registered as not-found-view to
136 136 pyramid.
137 137 """
138 138
139 139 if not getattr(request, 'vcs_call', None):
140 140 # handle like regular case with our error_handler
141 141 return error_handler(HTTPNotFound(), request)
142 142
143 143 # handle not found view as a vcs call
144 144 settings = request.registry.settings
145 145 ae_client = getattr(request, 'ae_client', None)
146 146 vcs_app = VCSMiddleware(
147 147 HTTPNotFound(), request.registry, settings,
148 148 appenlight_client=ae_client)
149 149
150 150 return wsgiapp(vcs_app)(None, request)
151 151
152 152
153 153 def error_handler(exception, request):
154 154 import rhodecode
155 155 from rhodecode.lib import helpers
156 156
157 157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
158 158
159 159 base_response = HTTPInternalServerError()
160 160 # prefer original exception for the response since it may have headers set
161 161 if isinstance(exception, HTTPException):
162 162 base_response = exception
163 163 elif isinstance(exception, VCSCommunicationError):
164 164 base_response = VCSServerUnavailable()
165 165
166 166 if is_http_error(base_response):
167 167 log.exception(
168 168 'error occurred handling this request for path: %s', request.path)
169 169
170 170 error_explanation = base_response.explanation or str(base_response)
171 171 if base_response.status_code == 404:
172 172 error_explanation += " Optionally you don't have permission to access this page."
173 173 c = AttributeDict()
174 174 c.error_message = base_response.status
175 175 c.error_explanation = error_explanation
176 176 c.visual = AttributeDict()
177 177
178 178 c.visual.rhodecode_support_url = (
179 179 request.registry.settings.get('rhodecode_support_url') or
180 180 request.route_url('rhodecode_support')
181 181 )
182 182 c.redirect_time = 0
183 183 c.rhodecode_name = rhodecode_title
184 184 if not c.rhodecode_name:
185 185 c.rhodecode_name = 'Rhodecode'
186 186
187 187 c.causes = []
188 188 if is_http_error(base_response):
189 189 c.causes.append('Server is overloaded.')
190 190 c.causes.append('Server database connection is lost.')
191 191 c.causes.append('Server expected unhandled error.')
192 192
193 193 if hasattr(base_response, 'causes'):
194 194 c.causes = base_response.causes
195 195
196 196 c.messages = helpers.flash.pop_messages(request=request)
197 197
198 198 exc_info = sys.exc_info()
199 199 c.exception_id = id(exc_info)
200 200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
201 201 or base_response.status_code > 499
202 202 c.exception_id_url = request.route_url(
203 203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
204 204
205 205 if c.show_exception_id:
206 206 store_exception(c.exception_id, exc_info)
207 207
208 208 response = render_to_response(
209 209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
210 210 response=base_response)
211 211
212 212 return response
213 213
214 214
215 215 def includeme_first(config):
216 216 # redirect automatic browser favicon.ico requests to correct place
217 217 def favicon_redirect(context, request):
218 218 return HTTPFound(
219 219 request.static_path('rhodecode:public/images/favicon.ico'))
220 220
221 221 config.add_view(favicon_redirect, route_name='favicon')
222 222 config.add_route('favicon', '/favicon.ico')
223 223
224 224 def robots_redirect(context, request):
225 225 return HTTPFound(
226 226 request.static_path('rhodecode:public/robots.txt'))
227 227
228 228 config.add_view(robots_redirect, route_name='robots')
229 229 config.add_route('robots', '/robots.txt')
230 230
231 231 config.add_static_view(
232 232 '_static/deform', 'deform:static')
233 233 config.add_static_view(
234 234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
235 235
236 236
237 237 def includeme(config):
238 238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
239 239 settings = config.registry.settings
240 240 config.set_request_factory(Request)
241 241
242 242 # plugin information
243 243 config.registry.rhodecode_plugins = collections.OrderedDict()
244 244
245 245 config.add_directive(
246 246 'register_rhodecode_plugin', register_rhodecode_plugin)
247 247
248 248 config.add_directive('configure_celery', configure_celery)
249 249
250 250 if asbool(settings.get('appenlight', 'false')):
251 251 config.include('appenlight_client.ext.pyramid_tween')
252 252
253 253 load_all = should_load_all()
254 254
255 255 # Includes which are required. The application would fail without them.
256 256 config.include('pyramid_mako')
257 257 config.include('rhodecode.lib.rc_beaker')
258 258 config.include('rhodecode.lib.rc_cache')
259 259
260 260 config.include('rhodecode.apps._base.navigation')
261 261 config.include('rhodecode.apps._base.subscribers')
262 262 config.include('rhodecode.tweens')
263 263 config.include('rhodecode.authentication')
264 264
265 265 if load_all:
266 266 config.include('rhodecode.integrations')
267 267
268 268 if load_all:
269 269 # load CE authentication plugins
270 270 config.include('rhodecode.authentication.plugins.auth_crowd')
271 271 config.include('rhodecode.authentication.plugins.auth_headers')
272 272 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
273 273 config.include('rhodecode.authentication.plugins.auth_ldap')
274 274 config.include('rhodecode.authentication.plugins.auth_pam')
275 275 config.include('rhodecode.authentication.plugins.auth_rhodecode')
276 276 config.include('rhodecode.authentication.plugins.auth_token')
277 277
278 278 # Auto discover authentication plugins and include their configuration.
279 279 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
280 280 from rhodecode.authentication import discover_legacy_plugins
281 281 discover_legacy_plugins(config)
282 282
283 283 # apps
284 284 if load_all:
285 285 config.include('rhodecode.apps._base')
286 286 config.include('rhodecode.apps.hovercards')
287 287 config.include('rhodecode.apps.ops')
288 288 config.include('rhodecode.apps.admin')
289 289 config.include('rhodecode.apps.channelstream')
290 290 config.include('rhodecode.apps.file_store')
291 291 config.include('rhodecode.apps.login')
292 292 config.include('rhodecode.apps.home')
293 293 config.include('rhodecode.apps.journal')
294 294 config.include('rhodecode.apps.repository')
295 295 config.include('rhodecode.apps.repo_group')
296 296 config.include('rhodecode.apps.user_group')
297 297 config.include('rhodecode.apps.search')
298 298 config.include('rhodecode.apps.user_profile')
299 299 config.include('rhodecode.apps.user_group_profile')
300 300 config.include('rhodecode.apps.my_account')
301 301 config.include('rhodecode.apps.svn_support')
302 302 config.include('rhodecode.apps.ssh_support')
303 303 config.include('rhodecode.apps.gist')
304 304 config.include('rhodecode.apps.debug_style')
305 305 config.include('rhodecode.api')
306 306
307 307 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
308 308 config.add_translation_dirs('rhodecode:i18n/')
309 309 settings['default_locale_name'] = settings.get('lang', 'en')
310 310
311 311 # Add subscribers.
312 312 if load_all:
313 313 config.add_subscriber(inject_app_settings,
314 314 pyramid.events.ApplicationCreated)
315 315 config.add_subscriber(scan_repositories_if_enabled,
316 316 pyramid.events.ApplicationCreated)
317 317 config.add_subscriber(write_metadata_if_needed,
318 318 pyramid.events.ApplicationCreated)
319 319 config.add_subscriber(write_usage_data,
320 320 pyramid.events.ApplicationCreated)
321 321 config.add_subscriber(write_js_routes_if_enabled,
322 322 pyramid.events.ApplicationCreated)
323 323
324 324 # request custom methods
325 325 config.add_request_method(
326 326 'rhodecode.lib.partial_renderer.get_partial_renderer',
327 327 'get_partial_renderer')
328 328
329 329 config.add_request_method(
330 330 'rhodecode.lib.request_counter.get_request_counter',
331 331 'request_count')
332 332
333 333 # Set the authorization policy.
334 334 authz_policy = ACLAuthorizationPolicy()
335 335 config.set_authorization_policy(authz_policy)
336 336
337 337 # Set the default renderer for HTML templates to mako.
338 338 config.add_mako_renderer('.html')
339 339
340 340 config.add_renderer(
341 341 name='json_ext',
342 342 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
343 343
344 344 config.add_renderer(
345 345 name='string_html',
346 346 factory='rhodecode.lib.string_renderer.html')
347 347
348 348 # include RhodeCode plugins
349 349 includes = aslist(settings.get('rhodecode.includes', []))
350 350 for inc in includes:
351 351 config.include(inc)
352 352
353 353 # custom not found view, if our pyramid app doesn't know how to handle
354 354 # the request pass it to potential VCS handling ap
355 355 config.add_notfound_view(not_found_view)
356 356 if not settings.get('debugtoolbar.enabled', False):
357 357 # disabled debugtoolbar handle all exceptions via the error_handlers
358 358 config.add_view(error_handler, context=Exception)
359 359
360 360 # all errors including 403/404/50X
361 361 config.add_view(error_handler, context=HTTPError)
362 362
363 363
364 364 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
365 365 """
366 366 Apply outer WSGI middlewares around the application.
367 367 """
368 368 registry = config.registry
369 369 settings = registry.settings
370 370
371 371 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
372 372 pyramid_app = HttpsFixup(pyramid_app, settings)
373 373
374 374 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
375 375 pyramid_app, settings)
376 376 registry.ae_client = _ae_client
377 377
378 378 if settings['gzip_responses']:
379 379 pyramid_app = make_gzip_middleware(
380 380 pyramid_app, settings, compress_level=1)
381 381
382 382 # this should be the outer most middleware in the wsgi stack since
383 383 # middleware like Routes make database calls
384 384 def pyramid_app_with_cleanup(environ, start_response):
385 385 try:
386 386 return pyramid_app(environ, start_response)
387 387 finally:
388 388 # Dispose current database session and rollback uncommitted
389 389 # transactions.
390 390 meta.Session.remove()
391 391
392 392 # In a single threaded mode server, on non sqlite db we should have
393 393 # '0 Current Checked out connections' at the end of a request,
394 394 # if not, then something, somewhere is leaving a connection open
395 395 pool = meta.Base.metadata.bind.engine.pool
396 396 log.debug('sa pool status: %s', pool.status())
397 397 log.debug('Request processing finalized')
398 398
399 399 return pyramid_app_with_cleanup
400 400
401 401
402 402 def sanitize_settings_and_apply_defaults(global_config, settings):
403 403 """
404 404 Applies settings defaults and does all type conversion.
405 405
406 406 We would move all settings parsing and preparation into this place, so that
407 407 we have only one place left which deals with this part. The remaining parts
408 408 of the application would start to rely fully on well prepared settings.
409 409
410 410 This piece would later be split up per topic to avoid a big fat monster
411 411 function.
412 412 """
413 413
414 414 settings.setdefault('rhodecode.edition', 'Community Edition')
415 settings.setdefault('rhodecode.edition_id', 'CE')
415 416
416 417 if 'mako.default_filters' not in settings:
417 418 # set custom default filters if we don't have it defined
418 419 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
419 420 settings['mako.default_filters'] = 'h_filter'
420 421
421 422 if 'mako.directories' not in settings:
422 423 mako_directories = settings.setdefault('mako.directories', [
423 424 # Base templates of the original application
424 425 'rhodecode:templates',
425 426 ])
426 427 log.debug(
427 428 "Using the following Mako template directories: %s",
428 429 mako_directories)
429 430
430 431 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
431 432 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
432 433 raw_url = settings['beaker.session.url']
433 434 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
434 435 settings['beaker.session.url'] = 'redis://' + raw_url
435 436
436 437 # Default includes, possible to change as a user
437 438 pyramid_includes = settings.setdefault('pyramid.includes', [])
438 439 log.debug(
439 440 "Using the following pyramid.includes: %s",
440 441 pyramid_includes)
441 442
442 443 # TODO: johbo: Re-think this, usually the call to config.include
443 444 # should allow to pass in a prefix.
444 445 settings.setdefault('rhodecode.api.url', '/_admin/api')
445 446 settings.setdefault('__file__', global_config.get('__file__'))
446 447
447 448 # Sanitize generic settings.
448 449 _list_setting(settings, 'default_encoding', 'UTF-8')
449 450 _bool_setting(settings, 'is_test', 'false')
450 451 _bool_setting(settings, 'gzip_responses', 'false')
451 452
452 453 # Call split out functions that sanitize settings for each topic.
453 454 _sanitize_appenlight_settings(settings)
454 455 _sanitize_vcs_settings(settings)
455 456 _sanitize_cache_settings(settings)
456 457
457 458 # configure instance id
458 459 config_utils.set_instance_id(settings)
459 460
460 461 return settings
461 462
462 463
463 464 def enable_debug():
464 465 """
465 466 Helper to enable debug on running instance
466 467 :return:
467 468 """
468 469 import tempfile
469 470 import textwrap
470 471 import logging.config
471 472
472 473 ini_template = textwrap.dedent("""
473 474 #####################################
474 475 ### DEBUG LOGGING CONFIGURATION ####
475 476 #####################################
476 477 [loggers]
477 478 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
478 479
479 480 [handlers]
480 481 keys = console, console_sql
481 482
482 483 [formatters]
483 484 keys = generic, color_formatter, color_formatter_sql
484 485
485 486 #############
486 487 ## LOGGERS ##
487 488 #############
488 489 [logger_root]
489 490 level = NOTSET
490 491 handlers = console
491 492
492 493 [logger_sqlalchemy]
493 494 level = INFO
494 495 handlers = console_sql
495 496 qualname = sqlalchemy.engine
496 497 propagate = 0
497 498
498 499 [logger_beaker]
499 500 level = DEBUG
500 501 handlers =
501 502 qualname = beaker.container
502 503 propagate = 1
503 504
504 505 [logger_rhodecode]
505 506 level = DEBUG
506 507 handlers =
507 508 qualname = rhodecode
508 509 propagate = 1
509 510
510 511 [logger_ssh_wrapper]
511 512 level = DEBUG
512 513 handlers =
513 514 qualname = ssh_wrapper
514 515 propagate = 1
515 516
516 517 [logger_celery]
517 518 level = DEBUG
518 519 handlers =
519 520 qualname = celery
520 521
521 522
522 523 ##############
523 524 ## HANDLERS ##
524 525 ##############
525 526
526 527 [handler_console]
527 528 class = StreamHandler
528 529 args = (sys.stderr, )
529 530 level = DEBUG
530 531 formatter = color_formatter
531 532
532 533 [handler_console_sql]
533 534 # "level = DEBUG" logs SQL queries and results.
534 535 # "level = INFO" logs SQL queries.
535 536 # "level = WARN" logs neither. (Recommended for production systems.)
536 537 class = StreamHandler
537 538 args = (sys.stderr, )
538 539 level = WARN
539 540 formatter = color_formatter_sql
540 541
541 542 ################
542 543 ## FORMATTERS ##
543 544 ################
544 545
545 546 [formatter_generic]
546 547 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
547 548 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
548 549 datefmt = %Y-%m-%d %H:%M:%S
549 550
550 551 [formatter_color_formatter]
551 552 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
552 553 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
553 554 datefmt = %Y-%m-%d %H:%M:%S
554 555
555 556 [formatter_color_formatter_sql]
556 557 class = rhodecode.lib.logging_formatter.ColorFormatterSql
557 558 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
558 559 datefmt = %Y-%m-%d %H:%M:%S
559 560 """)
560 561
561 562 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
562 563 delete=False) as f:
563 564 log.info('Saved Temporary DEBUG config at %s', f.name)
564 565 f.write(ini_template)
565 566
566 567 logging.config.fileConfig(f.name)
567 568 log.debug('DEBUG MODE ON')
568 569 os.remove(f.name)
569 570
570 571
571 572 def _sanitize_appenlight_settings(settings):
572 573 _bool_setting(settings, 'appenlight', 'false')
573 574
574 575
575 576 def _sanitize_vcs_settings(settings):
576 577 """
577 578 Applies settings defaults and does type conversion for all VCS related
578 579 settings.
579 580 """
580 581 _string_setting(settings, 'vcs.svn.compatible_version', '')
581 582 _string_setting(settings, 'vcs.hooks.protocol', 'http')
582 583 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
583 584 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
584 585 _string_setting(settings, 'vcs.server', '')
585 586 _string_setting(settings, 'vcs.server.protocol', 'http')
586 587 _bool_setting(settings, 'startup.import_repos', 'false')
587 588 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
588 589 _bool_setting(settings, 'vcs.server.enable', 'true')
589 590 _bool_setting(settings, 'vcs.start_server', 'false')
590 591 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
591 592 _int_setting(settings, 'vcs.connection_timeout', 3600)
592 593
593 594 # Support legacy values of vcs.scm_app_implementation. Legacy
594 595 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
595 596 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
596 597 scm_app_impl = settings['vcs.scm_app_implementation']
597 598 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
598 599 settings['vcs.scm_app_implementation'] = 'http'
599 600
600 601
601 602 def _sanitize_cache_settings(settings):
602 603 temp_store = tempfile.gettempdir()
603 604 default_cache_dir = os.path.join(temp_store, 'rc_cache')
604 605
605 606 # save default, cache dir, and use it for all backends later.
606 607 default_cache_dir = _string_setting(
607 608 settings,
608 609 'cache_dir',
609 610 default_cache_dir, lower=False, default_when_empty=True)
610 611
611 612 # ensure we have our dir created
612 613 if not os.path.isdir(default_cache_dir):
613 614 os.makedirs(default_cache_dir, mode=0o755)
614 615
615 616 # exception store cache
616 617 _string_setting(
617 618 settings,
618 619 'exception_tracker.store_path',
619 620 temp_store, lower=False, default_when_empty=True)
620 621 _bool_setting(
621 622 settings,
622 623 'exception_tracker.send_email',
623 624 'false')
624 625 _string_setting(
625 626 settings,
626 627 'exception_tracker.email_prefix',
627 628 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
628 629
629 630 # cache_perms
630 631 _string_setting(
631 632 settings,
632 633 'rc_cache.cache_perms.backend',
633 634 'dogpile.cache.rc.file_namespace', lower=False)
634 635 _int_setting(
635 636 settings,
636 637 'rc_cache.cache_perms.expiration_time',
637 638 60)
638 639 _string_setting(
639 640 settings,
640 641 'rc_cache.cache_perms.arguments.filename',
641 642 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
642 643
643 644 # cache_repo
644 645 _string_setting(
645 646 settings,
646 647 'rc_cache.cache_repo.backend',
647 648 'dogpile.cache.rc.file_namespace', lower=False)
648 649 _int_setting(
649 650 settings,
650 651 'rc_cache.cache_repo.expiration_time',
651 652 60)
652 653 _string_setting(
653 654 settings,
654 655 'rc_cache.cache_repo.arguments.filename',
655 656 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
656 657
657 658 # cache_license
658 659 _string_setting(
659 660 settings,
660 661 'rc_cache.cache_license.backend',
661 662 'dogpile.cache.rc.file_namespace', lower=False)
662 663 _int_setting(
663 664 settings,
664 665 'rc_cache.cache_license.expiration_time',
665 666 5*60)
666 667 _string_setting(
667 668 settings,
668 669 'rc_cache.cache_license.arguments.filename',
669 670 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
670 671
671 672 # cache_repo_longterm memory, 96H
672 673 _string_setting(
673 674 settings,
674 675 'rc_cache.cache_repo_longterm.backend',
675 676 'dogpile.cache.rc.memory_lru', lower=False)
676 677 _int_setting(
677 678 settings,
678 679 'rc_cache.cache_repo_longterm.expiration_time',
679 680 345600)
680 681 _int_setting(
681 682 settings,
682 683 'rc_cache.cache_repo_longterm.max_size',
683 684 10000)
684 685
685 686 # sql_cache_short
686 687 _string_setting(
687 688 settings,
688 689 'rc_cache.sql_cache_short.backend',
689 690 'dogpile.cache.rc.memory_lru', lower=False)
690 691 _int_setting(
691 692 settings,
692 693 'rc_cache.sql_cache_short.expiration_time',
693 694 30)
694 695 _int_setting(
695 696 settings,
696 697 'rc_cache.sql_cache_short.max_size',
697 698 10000)
698 699
699 700
700 701 def _int_setting(settings, name, default):
701 702 settings[name] = int(settings.get(name, default))
702 703 return settings[name]
703 704
704 705
705 706 def _bool_setting(settings, name, default):
706 707 input_val = settings.get(name, default)
707 708 if isinstance(input_val, unicode):
708 709 input_val = input_val.encode('utf8')
709 710 settings[name] = asbool(input_val)
710 711 return settings[name]
711 712
712 713
713 714 def _list_setting(settings, name, default):
714 715 raw_value = settings.get(name, default)
715 716
716 717 old_separator = ','
717 718 if old_separator in raw_value:
718 719 # If we get a comma separated list, pass it to our own function.
719 720 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
720 721 else:
721 722 # Otherwise we assume it uses pyramids space/newline separation.
722 723 settings[name] = aslist(raw_value)
723 724 return settings[name]
724 725
725 726
726 727 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
727 728 value = settings.get(name, default)
728 729
729 730 if default_when_empty and not value:
730 731 # use default value when value is empty
731 732 value = default
732 733
733 734 if lower:
734 735 value = value.lower()
735 736 settings[name] = value
736 737 return settings[name]
737 738
738 739
739 740 def _substitute_values(mapping, substitutions):
740 741 result = {}
741 742
742 743 try:
743 744 for key, value in mapping.items():
744 745 # initialize without substitution first
745 746 result[key] = value
746 747
747 748 # Note: Cannot use regular replacements, since they would clash
748 749 # with the implementation of ConfigParser. Using "format" instead.
749 750 try:
750 751 result[key] = value.format(**substitutions)
751 752 except KeyError as e:
752 753 env_var = '{}'.format(e.args[0])
753 754
754 755 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
755 756 'Make sure your environment has {var} set, or remove this ' \
756 757 'variable from config file'.format(key=key, var=env_var)
757 758
758 759 if env_var.startswith('ENV_'):
759 760 raise ValueError(msg)
760 761 else:
761 762 log.warning(msg)
762 763
763 764 except ValueError as e:
764 765 log.warning('Failed to substitute ENV variable: %s', e)
765 766 result = mapping
766 767
767 768 return result
@@ -1,618 +1,619 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 markupsafe
31 31 import ipaddress
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
37 37 import rhodecode
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.authentication.base import VCS_TYPE
40 40 from rhodecode.lib import auth, utils2
41 41 from rhodecode.lib import helpers as h
42 42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 43 from rhodecode.lib.exceptions import UserCreationError
44 44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 45 from rhodecode.lib.utils2 import (
46 46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 48 from rhodecode.model.notification import NotificationModel
49 49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def _filter_proxy(ip):
55 55 """
56 56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 57 ips. Those comma separated IPs are passed from various proxies in the
58 58 chain of request processing. The left-most being the original client.
59 59 We only care about the first IP which came from the org. client.
60 60
61 61 :param ip: ip string from headers
62 62 """
63 63 if ',' in ip:
64 64 _ips = ip.split(',')
65 65 _first_ip = _ips[0].strip()
66 66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 67 return _first_ip
68 68 return ip
69 69
70 70
71 71 def _filter_port(ip):
72 72 """
73 73 Removes a port from ip, there are 4 main cases to handle here.
74 74 - ipv4 eg. 127.0.0.1
75 75 - ipv6 eg. ::1
76 76 - ipv4+port eg. 127.0.0.1:8080
77 77 - ipv6+port eg. [::1]:8080
78 78
79 79 :param ip:
80 80 """
81 81 def is_ipv6(ip_addr):
82 82 if hasattr(socket, 'inet_pton'):
83 83 try:
84 84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 85 except socket.error:
86 86 return False
87 87 else:
88 88 # fallback to ipaddress
89 89 try:
90 90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 91 except Exception:
92 92 return False
93 93 return True
94 94
95 95 if ':' not in ip: # must be ipv4 pure ip
96 96 return ip
97 97
98 98 if '[' in ip and ']' in ip: # ipv6 with port
99 99 return ip.split(']')[0][1:].lower()
100 100
101 101 # must be ipv6 or ipv4 with port
102 102 if is_ipv6(ip):
103 103 return ip
104 104 else:
105 105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 106 return ip
107 107
108 108
109 109 def get_ip_addr(environ):
110 110 proxy_key = 'HTTP_X_REAL_IP'
111 111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 112 def_key = 'REMOTE_ADDR'
113 113 _filters = lambda x: _filter_port(_filter_proxy(x))
114 114
115 115 ip = environ.get(proxy_key)
116 116 if ip:
117 117 return _filters(ip)
118 118
119 119 ip = environ.get(proxy_key2)
120 120 if ip:
121 121 return _filters(ip)
122 122
123 123 ip = environ.get(def_key, '0.0.0.0')
124 124 return _filters(ip)
125 125
126 126
127 127 def get_server_ip_addr(environ, log_errors=True):
128 128 hostname = environ.get('SERVER_NAME')
129 129 try:
130 130 return socket.gethostbyname(hostname)
131 131 except Exception as e:
132 132 if log_errors:
133 133 # in some cases this lookup is not possible, and we don't want to
134 134 # make it an exception in logs
135 135 log.exception('Could not retrieve server ip address: %s', e)
136 136 return hostname
137 137
138 138
139 139 def get_server_port(environ):
140 140 return environ.get('SERVER_PORT')
141 141
142 142
143 143 def get_access_path(environ):
144 144 path = environ.get('PATH_INFO')
145 145 org_req = environ.get('pylons.original_request')
146 146 if org_req:
147 147 path = org_req.environ.get('PATH_INFO')
148 148 return path
149 149
150 150
151 151 def get_user_agent(environ):
152 152 return environ.get('HTTP_USER_AGENT')
153 153
154 154
155 155 def vcs_operation_context(
156 156 environ, repo_name, username, action, scm, check_locking=True,
157 157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 158 """
159 159 Generate the context for a vcs operation, e.g. push or pull.
160 160
161 161 This context is passed over the layers so that hooks triggered by the
162 162 vcs operation know details like the user, the user's IP address etc.
163 163
164 164 :param check_locking: Allows to switch of the computation of the locking
165 165 data. This serves mainly the need of the simplevcs middleware to be
166 166 able to disable this for certain operations.
167 167
168 168 """
169 169 # Tri-state value: False: unlock, None: nothing, True: lock
170 170 make_lock = None
171 171 locked_by = [None, None, None]
172 172 is_anonymous = username == User.DEFAULT_USER
173 173 user = User.get_by_username(username)
174 174 if not is_anonymous and check_locking:
175 175 log.debug('Checking locking on repository "%s"', repo_name)
176 176 repo = Repository.get_by_repo_name(repo_name)
177 177 make_lock, __, locked_by = repo.get_locking_state(
178 178 action, user.user_id)
179 179 user_id = user.user_id
180 180 settings_model = VcsSettingsModel(repo=repo_name)
181 181 ui_settings = settings_model.get_ui_settings()
182 182
183 183 # NOTE(marcink): This should be also in sync with
184 184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 185 store = [x for x in ui_settings if x.key == '/']
186 186 repo_store = ''
187 187 if store:
188 188 repo_store = store[0].value
189 189
190 190 scm_data = {
191 191 'ip': get_ip_addr(environ),
192 192 'username': username,
193 193 'user_id': user_id,
194 194 'action': action,
195 195 'repository': repo_name,
196 196 'scm': scm,
197 197 'config': rhodecode.CONFIG['__file__'],
198 198 'repo_store': repo_store,
199 199 'make_lock': make_lock,
200 200 'locked_by': locked_by,
201 201 'server_url': utils2.get_server_url(environ),
202 202 'user_agent': get_user_agent(environ),
203 203 'hooks': get_enabled_hook_classes(ui_settings),
204 204 'is_shadow_repo': is_shadow_repo,
205 205 'detect_force_push': detect_force_push,
206 206 'check_branch_perms': check_branch_perms,
207 207 }
208 208 return scm_data
209 209
210 210
211 211 class BasicAuth(AuthBasicAuthenticator):
212 212
213 213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
215 215 self.realm = realm
216 216 self.rc_realm = rc_realm
217 217 self.initial_call = initial_call_detection
218 218 self.authfunc = authfunc
219 219 self.registry = registry
220 220 self.acl_repo_name = acl_repo_name
221 221 self._rc_auth_http_code = auth_http_code
222 222
223 223 def _get_response_from_code(self, http_code):
224 224 try:
225 225 return get_exception(safe_int(http_code))
226 226 except Exception:
227 227 log.exception('Failed to fetch response for code %s', http_code)
228 228 return HTTPForbidden
229 229
230 230 def get_rc_realm(self):
231 231 return safe_str(self.rc_realm)
232 232
233 233 def build_authentication(self):
234 234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
235 235 if self._rc_auth_http_code and not self.initial_call:
236 236 # return alternative HTTP code if alternative http return code
237 237 # is specified in RhodeCode config, but ONLY if it's not the
238 238 # FIRST call
239 239 custom_response_klass = self._get_response_from_code(
240 240 self._rc_auth_http_code)
241 241 return custom_response_klass(headers=head)
242 242 return HTTPUnauthorized(headers=head)
243 243
244 244 def authenticate(self, environ):
245 245 authorization = AUTHORIZATION(environ)
246 246 if not authorization:
247 247 return self.build_authentication()
248 248 (authmeth, auth) = authorization.split(' ', 1)
249 249 if 'basic' != authmeth.lower():
250 250 return self.build_authentication()
251 251 auth = auth.strip().decode('base64')
252 252 _parts = auth.split(':', 1)
253 253 if len(_parts) == 2:
254 254 username, password = _parts
255 255 auth_data = self.authfunc(
256 256 username, password, environ, VCS_TYPE,
257 257 registry=self.registry, acl_repo_name=self.acl_repo_name)
258 258 if auth_data:
259 259 return {'username': username, 'auth_data': auth_data}
260 260 if username and password:
261 261 # we mark that we actually executed authentication once, at
262 262 # that point we can use the alternative auth code
263 263 self.initial_call = False
264 264
265 265 return self.build_authentication()
266 266
267 267 __call__ = authenticate
268 268
269 269
270 270 def calculate_version_hash(config):
271 271 return sha1(
272 272 config.get('beaker.session.secret', '') +
273 273 rhodecode.__version__)[:8]
274 274
275 275
276 276 def get_current_lang(request):
277 277 # NOTE(marcink): remove after pyramid move
278 278 try:
279 279 return translation.get_lang()[0]
280 280 except:
281 281 pass
282 282
283 283 return getattr(request, '_LOCALE_', request.locale_name)
284 284
285 285
286 286 def attach_context_attributes(context, request, user_id=None, is_api=None):
287 287 """
288 288 Attach variables into template context called `c`.
289 289 """
290 290 config = request.registry.settings
291 291
292 292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
293 293 context.rc_config = rc_config
294 294 context.rhodecode_version = rhodecode.__version__
295 295 context.rhodecode_edition = config.get('rhodecode.edition')
296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
296 297 # unique secret + version does not leak the version but keep consistency
297 298 context.rhodecode_version_hash = calculate_version_hash(config)
298 299
299 300 # Default language set for the incoming request
300 301 context.language = get_current_lang(request)
301 302
302 303 # Visual options
303 304 context.visual = AttributeDict({})
304 305
305 306 # DB stored Visual Items
306 307 context.visual.show_public_icon = str2bool(
307 308 rc_config.get('rhodecode_show_public_icon'))
308 309 context.visual.show_private_icon = str2bool(
309 310 rc_config.get('rhodecode_show_private_icon'))
310 311 context.visual.stylify_metatags = str2bool(
311 312 rc_config.get('rhodecode_stylify_metatags'))
312 313 context.visual.dashboard_items = safe_int(
313 314 rc_config.get('rhodecode_dashboard_items', 100))
314 315 context.visual.admin_grid_items = safe_int(
315 316 rc_config.get('rhodecode_admin_grid_items', 100))
316 317 context.visual.show_revision_number = str2bool(
317 318 rc_config.get('rhodecode_show_revision_number', True))
318 319 context.visual.show_sha_length = safe_int(
319 320 rc_config.get('rhodecode_show_sha_length', 100))
320 321 context.visual.repository_fields = str2bool(
321 322 rc_config.get('rhodecode_repository_fields'))
322 323 context.visual.show_version = str2bool(
323 324 rc_config.get('rhodecode_show_version'))
324 325 context.visual.use_gravatar = str2bool(
325 326 rc_config.get('rhodecode_use_gravatar'))
326 327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
327 328 context.visual.default_renderer = rc_config.get(
328 329 'rhodecode_markup_renderer', 'rst')
329 330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
330 331 context.visual.rhodecode_support_url = \
331 332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
332 333
333 334 context.visual.affected_files_cut_off = 60
334 335
335 336 context.pre_code = rc_config.get('rhodecode_pre_code')
336 337 context.post_code = rc_config.get('rhodecode_post_code')
337 338 context.rhodecode_name = rc_config.get('rhodecode_title')
338 339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
339 340 # if we have specified default_encoding in the request, it has more
340 341 # priority
341 342 if request.GET.get('default_encoding'):
342 343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
343 344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
344 345 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
345 346
346 347 # INI stored
347 348 context.labs_active = str2bool(
348 349 config.get('labs_settings_active', 'false'))
349 350 context.ssh_enabled = str2bool(
350 351 config.get('ssh.generate_authorized_keyfile', 'false'))
351 352 context.ssh_key_generator_enabled = str2bool(
352 353 config.get('ssh.enable_ui_key_generator', 'true'))
353 354
354 355 context.visual.allow_repo_location_change = str2bool(
355 356 config.get('allow_repo_location_change', True))
356 357 context.visual.allow_custom_hooks_settings = str2bool(
357 358 config.get('allow_custom_hooks_settings', True))
358 359 context.debug_style = str2bool(config.get('debug_style', False))
359 360
360 361 context.rhodecode_instanceid = config.get('instance_id')
361 362
362 363 context.visual.cut_off_limit_diff = safe_int(
363 364 config.get('cut_off_limit_diff'))
364 365 context.visual.cut_off_limit_file = safe_int(
365 366 config.get('cut_off_limit_file'))
366 367
367 368 context.license = AttributeDict({})
368 369 context.license.hide_license_info = str2bool(
369 370 config.get('license.hide_license_info', False))
370 371
371 372 # AppEnlight
372 373 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
373 374 context.appenlight_api_public_key = config.get(
374 375 'appenlight.api_public_key', '')
375 376 context.appenlight_server_url = config.get('appenlight.server_url', '')
376 377
377 378 diffmode = {
378 379 "unified": "unified",
379 380 "sideside": "sideside"
380 381 }.get(request.GET.get('diffmode'))
381 382
382 383 if is_api is not None:
383 384 is_api = hasattr(request, 'rpc_user')
384 385 session_attrs = {
385 386 # defaults
386 387 "clone_url_format": "http",
387 388 "diffmode": "sideside",
388 389 "license_fingerprint": request.session.get('license_fingerprint')
389 390 }
390 391
391 392 if not is_api:
392 393 # don't access pyramid session for API calls
393 394 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
394 395 request.session['rc_user_session_attr.diffmode'] = diffmode
395 396
396 397 # session settings per user
397 398
398 399 for k, v in request.session.items():
399 400 pref = 'rc_user_session_attr.'
400 401 if k and k.startswith(pref):
401 402 k = k[len(pref):]
402 403 session_attrs[k] = v
403 404
404 405 context.user_session_attrs = session_attrs
405 406
406 407 # JS template context
407 408 context.template_context = {
408 409 'repo_name': None,
409 410 'repo_type': None,
410 411 'repo_landing_commit': None,
411 412 'rhodecode_user': {
412 413 'username': None,
413 414 'email': None,
414 415 'notification_status': False
415 416 },
416 417 'session_attrs': session_attrs,
417 418 'visual': {
418 419 'default_renderer': None
419 420 },
420 421 'commit_data': {
421 422 'commit_id': None
422 423 },
423 424 'pull_request_data': {'pull_request_id': None},
424 425 'timeago': {
425 426 'refresh_time': 120 * 1000,
426 427 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
427 428 },
428 429 'pyramid_dispatch': {
429 430
430 431 },
431 432 'extra': {'plugins': {}}
432 433 }
433 434 # END CONFIG VARS
434 435 if is_api:
435 436 csrf_token = None
436 437 else:
437 438 csrf_token = auth.get_csrf_token(session=request.session)
438 439
439 440 context.csrf_token = csrf_token
440 441 context.backends = rhodecode.BACKENDS.keys()
441 442
442 443 unread_count = 0
443 444 user_bookmark_list = []
444 445 if user_id:
445 446 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
446 447 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
447 448 context.unread_notifications = unread_count
448 449 context.bookmark_items = user_bookmark_list
449 450
450 451 # web case
451 452 if hasattr(request, 'user'):
452 453 context.auth_user = request.user
453 454 context.rhodecode_user = request.user
454 455
455 456 # api case
456 457 if hasattr(request, 'rpc_user'):
457 458 context.auth_user = request.rpc_user
458 459 context.rhodecode_user = request.rpc_user
459 460
460 461 # attach the whole call context to the request
461 462 request.call_context = context
462 463
463 464
464 465 def get_auth_user(request):
465 466 environ = request.environ
466 467 session = request.session
467 468
468 469 ip_addr = get_ip_addr(environ)
469 470
470 471 # make sure that we update permissions each time we call controller
471 472 _auth_token = (request.GET.get('auth_token', '') or request.GET.get('api_key', ''))
472 473 if not _auth_token and request.matchdict:
473 474 url_auth_token = request.matchdict.get('_auth_token')
474 475 _auth_token = url_auth_token
475 476 if _auth_token:
476 477 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
477 478
478 479 if _auth_token:
479 480 # when using API_KEY we assume user exists, and
480 481 # doesn't need auth based on cookies.
481 482 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
482 483 authenticated = False
483 484 else:
484 485 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
485 486 try:
486 487 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
487 488 ip_addr=ip_addr)
488 489 except UserCreationError as e:
489 490 h.flash(e, 'error')
490 491 # container auth or other auth functions that create users
491 492 # on the fly can throw this exception signaling that there's
492 493 # issue with user creation, explanation should be provided
493 494 # in Exception itself. We then create a simple blank
494 495 # AuthUser
495 496 auth_user = AuthUser(ip_addr=ip_addr)
496 497
497 498 # in case someone changes a password for user it triggers session
498 499 # flush and forces a re-login
499 500 if password_changed(auth_user, session):
500 501 session.invalidate()
501 502 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
502 503 auth_user = AuthUser(ip_addr=ip_addr)
503 504
504 505 authenticated = cookie_store.get('is_authenticated')
505 506
506 507 if not auth_user.is_authenticated and auth_user.is_user_object:
507 508 # user is not authenticated and not empty
508 509 auth_user.set_authenticated(authenticated)
509 510
510 511 return auth_user, _auth_token
511 512
512 513
513 514 def h_filter(s):
514 515 """
515 516 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
516 517 we wrap this with additional functionality that converts None to empty
517 518 strings
518 519 """
519 520 if s is None:
520 521 return markupsafe.Markup()
521 522 return markupsafe.escape(s)
522 523
523 524
524 525 def add_events_routes(config):
525 526 """
526 527 Adds routing that can be used in events. Because some events are triggered
527 528 outside of pyramid context, we need to bootstrap request with some
528 529 routing registered
529 530 """
530 531
531 532 from rhodecode.apps._base import ADMIN_PREFIX
532 533
533 534 config.add_route(name='home', pattern='/')
534 535 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
535 536 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
536 537
537 538 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
538 539 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
539 540 config.add_route(name='repo_summary', pattern='/{repo_name}')
540 541 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
541 542 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
542 543
543 544 config.add_route(name='pullrequest_show',
544 545 pattern='/{repo_name}/pull-request/{pull_request_id}')
545 546 config.add_route(name='pull_requests_global',
546 547 pattern='/pull-request/{pull_request_id}')
547 548
548 549 config.add_route(name='repo_commit',
549 550 pattern='/{repo_name}/changeset/{commit_id}')
550 551 config.add_route(name='repo_files',
551 552 pattern='/{repo_name}/files/{commit_id}/{f_path}')
552 553
553 554 config.add_route(name='hovercard_user',
554 555 pattern='/_hovercard/user/{user_id}')
555 556
556 557 config.add_route(name='hovercard_user_group',
557 558 pattern='/_hovercard/user_group/{user_group_id}')
558 559
559 560 config.add_route(name='hovercard_pull_request',
560 561 pattern='/_hovercard/pull_request/{pull_request_id}')
561 562
562 563 config.add_route(name='hovercard_repo_commit',
563 564 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
564 565
565 566
566 567 def bootstrap_config(request):
567 568 import pyramid.testing
568 569 registry = pyramid.testing.Registry('RcTestRegistry')
569 570
570 571 config = pyramid.testing.setUp(registry=registry, request=request)
571 572
572 573 # allow pyramid lookup in testing
573 574 config.include('pyramid_mako')
574 575 config.include('rhodecode.lib.rc_beaker')
575 576 config.include('rhodecode.lib.rc_cache')
576 577
577 578 add_events_routes(config)
578 579
579 580 return config
580 581
581 582
582 583 def bootstrap_request(**kwargs):
583 584 import pyramid.testing
584 585
585 586 class TestRequest(pyramid.testing.DummyRequest):
586 587 application_url = kwargs.pop('application_url', 'http://example.com')
587 588 host = kwargs.pop('host', 'example.com:80')
588 589 domain = kwargs.pop('domain', 'example.com')
589 590
590 591 def translate(self, msg):
591 592 return msg
592 593
593 594 def plularize(self, singular, plural, n):
594 595 return singular
595 596
596 597 def get_partial_renderer(self, tmpl_name):
597 598
598 599 from rhodecode.lib.partial_renderer import get_partial_renderer
599 600 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
600 601
601 602 _call_context = TemplateArgs()
602 603 _call_context.visual = TemplateArgs()
603 604 _call_context.visual.show_sha_length = 12
604 605 _call_context.visual.show_revision_number = True
605 606
606 607 @property
607 608 def call_context(self):
608 609 return self._call_context
609 610
610 611 class TestDummySession(pyramid.testing.DummySession):
611 612 def save(*arg, **kw):
612 613 pass
613 614
614 615 request = TestRequest(**kwargs)
615 616 request.session = TestDummySession()
616 617
617 618 return request
618 619
@@ -1,3238 +1,3239 b''
1 1 //Primary CSS
2 2
3 3 //--- IMPORTS ------------------//
4 4
5 5 @import 'helpers';
6 6 @import 'mixins';
7 7 @import 'rcicons';
8 8 @import 'variables';
9 9 @import 'bootstrap-variables';
10 10 @import 'form-bootstrap';
11 11 @import 'codemirror';
12 12 @import 'legacy_code_styles';
13 13 @import 'readme-box';
14 14 @import 'progress-bar';
15 15
16 16 @import 'type';
17 17 @import 'alerts';
18 18 @import 'buttons';
19 19 @import 'tags';
20 20 @import 'code-block';
21 21 @import 'examples';
22 22 @import 'login';
23 23 @import 'main-content';
24 24 @import 'select2';
25 25 @import 'comments';
26 26 @import 'panels-bootstrap';
27 27 @import 'panels';
28 28 @import 'deform';
29 29 @import 'tooltips';
30 30 @import 'sweetalert2';
31 31
32 32
33 33 //--- BASE ------------------//
34 34 .noscript-error {
35 35 top: 0;
36 36 left: 0;
37 37 width: 100%;
38 38 z-index: 101;
39 39 text-align: center;
40 40 font-size: 120%;
41 41 color: white;
42 42 background-color: @alert2;
43 43 padding: 5px 0 5px 0;
44 44 font-weight: @text-semibold-weight;
45 45 font-family: @text-semibold;
46 46 }
47 47
48 48 html {
49 49 display: table;
50 50 height: 100%;
51 51 width: 100%;
52 52 }
53 53
54 54 body {
55 55 display: table-cell;
56 56 width: 100%;
57 57 }
58 58
59 59 //--- LAYOUT ------------------//
60 60
61 61 .hidden{
62 62 display: none !important;
63 63 }
64 64
65 65 .box{
66 66 float: left;
67 67 width: 100%;
68 68 }
69 69
70 70 .browser-header {
71 71 clear: both;
72 72 }
73 73 .main {
74 74 clear: both;
75 75 padding:0 0 @pagepadding;
76 76 height: auto;
77 77
78 78 &:after { //clearfix
79 79 content:"";
80 80 clear:both;
81 81 width:100%;
82 82 display:block;
83 83 }
84 84 }
85 85
86 86 .flex-container {
87 87 display: flex;
88 88 justify-content: space-between;
89 89 }
90 90
91 91 .action-link{
92 92 margin-left: @padding;
93 93 padding-left: @padding;
94 94 border-left: @border-thickness solid @border-default-color;
95 95 }
96 96
97 97 .cursor-pointer {
98 98 cursor: pointer;
99 99 }
100 100
101 101 input + .action-link, .action-link.first{
102 102 border-left: none;
103 103 }
104 104
105 105 .link-disabled {
106 106 color: @grey4;
107 107 cursor: default;
108 108 }
109 109
110 110 .action-link.last{
111 111 margin-right: @padding;
112 112 padding-right: @padding;
113 113 }
114 114
115 115 .action-link.active,
116 116 .action-link.active a{
117 117 color: @grey4;
118 118 }
119 119
120 120 .action-link.disabled {
121 121 color: @grey4;
122 122 cursor: inherit;
123 123 }
124 124
125 125 .grey-link-action {
126 126 cursor: pointer;
127 127 &:hover {
128 128 color: @grey2;
129 129 }
130 130 color: @grey4;
131 131 }
132 132
133 133 .clipboard-action {
134 134 cursor: pointer;
135 135 margin-left: 5px;
136 136
137 137 &:not(.no-grey) {
138 138
139 139 &:hover {
140 140 color: @grey2;
141 141 }
142 142 color: @grey4;
143 143 }
144 144 }
145 145
146 146 ul.simple-list{
147 147 list-style: none;
148 148 margin: 0;
149 149 padding: 0;
150 150 }
151 151
152 152 .main-content {
153 153 padding-bottom: @pagepadding;
154 154 }
155 155
156 156 .wide-mode-wrapper {
157 157 max-width:4000px !important;
158 158 }
159 159
160 160 .wrapper {
161 161 position: relative;
162 162 max-width: @wrapper-maxwidth;
163 163 margin: 0 auto;
164 164 }
165 165
166 166 #content {
167 167 clear: both;
168 168 padding: 0 @contentpadding;
169 169 }
170 170
171 171 .advanced-settings-fields{
172 172 input{
173 173 margin-left: @textmargin;
174 174 margin-right: @padding/2;
175 175 }
176 176 }
177 177
178 178 .cs_files_title {
179 179 margin: @pagepadding 0 0;
180 180 }
181 181
182 182 input.inline[type="file"] {
183 183 display: inline;
184 184 }
185 185
186 186 .error_page {
187 187 margin: 10% auto;
188 188
189 189 h1 {
190 190 color: @grey2;
191 191 }
192 192
193 193 .alert {
194 194 margin: @padding 0;
195 195 }
196 196
197 197 .error-branding {
198 198 color: @grey4;
199 199 font-weight: @text-semibold-weight;
200 200 font-family: @text-semibold;
201 201 }
202 202
203 203 .error_message {
204 204 font-family: @text-regular;
205 205 }
206 206
207 207 .sidebar {
208 208 min-height: 275px;
209 209 margin: 0;
210 210 padding: 0 0 @sidebarpadding @sidebarpadding;
211 211 border: none;
212 212 }
213 213
214 214 .main-content {
215 215 position: relative;
216 216 margin: 0 @sidebarpadding @sidebarpadding;
217 217 padding: 0 0 0 @sidebarpadding;
218 218 border-left: @border-thickness solid @grey5;
219 219
220 220 @media (max-width:767px) {
221 221 clear: both;
222 222 width: 100%;
223 223 margin: 0;
224 224 border: none;
225 225 }
226 226 }
227 227
228 228 .inner-column {
229 229 float: left;
230 230 width: 29.75%;
231 231 min-height: 150px;
232 232 margin: @sidebarpadding 2% 0 0;
233 233 padding: 0 2% 0 0;
234 234 border-right: @border-thickness solid @grey5;
235 235
236 236 @media (max-width:767px) {
237 237 clear: both;
238 238 width: 100%;
239 239 border: none;
240 240 }
241 241
242 242 ul {
243 243 padding-left: 1.25em;
244 244 }
245 245
246 246 &:last-child {
247 247 margin: @sidebarpadding 0 0;
248 248 border: none;
249 249 }
250 250
251 251 h4 {
252 252 margin: 0 0 @padding;
253 253 font-weight: @text-semibold-weight;
254 254 font-family: @text-semibold;
255 255 }
256 256 }
257 257 }
258 258 .error-page-logo {
259 259 width: 130px;
260 260 height: 160px;
261 261 }
262 262
263 263 // HEADER
264 264 .header {
265 265
266 266 // TODO: johbo: Fix login pages, so that they work without a min-height
267 267 // for the header and then remove the min-height. I chose a smaller value
268 268 // intentionally here to avoid rendering issues in the main navigation.
269 269 min-height: 49px;
270 270 min-width: 1024px;
271 271
272 272 position: relative;
273 273 vertical-align: bottom;
274 274 padding: 0 @header-padding;
275 275 background-color: @grey1;
276 276 color: @grey5;
277 277
278 278 .title {
279 279 overflow: visible;
280 280 }
281 281
282 282 &:before,
283 283 &:after {
284 284 content: "";
285 285 clear: both;
286 286 width: 100%;
287 287 }
288 288
289 289 // TODO: johbo: Avoids breaking "Repositories" chooser
290 290 .select2-container .select2-choice .select2-arrow {
291 291 display: none;
292 292 }
293 293 }
294 294
295 295 #header-inner {
296 296 &.title {
297 297 margin: 0;
298 298 }
299 299 &:before,
300 300 &:after {
301 301 content: "";
302 302 clear: both;
303 303 }
304 304 }
305 305
306 306 // Gists
307 307 #files_data {
308 308 clear: both; //for firefox
309 309 padding-top: 10px;
310 310 }
311 311
312 312 #gistid {
313 313 margin-right: @padding;
314 314 }
315 315
316 316 // Global Settings Editor
317 317 .textarea.editor {
318 318 float: left;
319 319 position: relative;
320 320 max-width: @texteditor-width;
321 321
322 322 select {
323 323 position: absolute;
324 324 top:10px;
325 325 right:0;
326 326 }
327 327
328 328 .CodeMirror {
329 329 margin: 0;
330 330 }
331 331
332 332 .help-block {
333 333 margin: 0 0 @padding;
334 334 padding:.5em;
335 335 background-color: @grey6;
336 336 &.pre-formatting {
337 337 white-space: pre;
338 338 }
339 339 }
340 340 }
341 341
342 342 ul.auth_plugins {
343 343 margin: @padding 0 @padding @legend-width;
344 344 padding: 0;
345 345
346 346 li {
347 347 margin-bottom: @padding;
348 348 line-height: 1em;
349 349 list-style-type: none;
350 350
351 351 .auth_buttons .btn {
352 352 margin-right: @padding;
353 353 }
354 354
355 355 }
356 356 }
357 357
358 358
359 359 // My Account PR list
360 360
361 361 #show_closed {
362 362 margin: 0 1em 0 0;
363 363 }
364 364
365 365 #pull_request_list_table {
366 366 .closed {
367 367 background-color: @grey6;
368 368 }
369 369
370 370 .state-creating,
371 371 .state-updating,
372 372 .state-merging
373 373 {
374 374 background-color: @grey6;
375 375 }
376 376
377 377 .log-container .truncate {
378 378 height: 2.75em;
379 379 white-space: pre-line;
380 380 }
381 381 table.rctable .user {
382 382 padding-left: 0;
383 383 }
384 384 .td-status {
385 385 padding: 0 0px 0px 10px;
386 386 width: 15px;
387 387 }
388 388 table.rctable {
389 389 td.td-description,
390 390 .rc-user {
391 391 min-width: auto;
392 392 }
393 393 }
394 394 }
395 395
396 396 // Pull Requests
397 397
398 398 .pullrequests_section_head {
399 399 display: block;
400 400 clear: both;
401 401 margin: @padding 0;
402 402 font-weight: @text-bold-weight;
403 403 font-family: @text-bold;
404 404 }
405 405
406 406 .pr-commit-flow {
407 407 position: relative;
408 408 font-weight: 600;
409 409
410 410 .tag {
411 411 display: inline-block;
412 412 margin: 0 1em .5em 0;
413 413 }
414 414
415 415 .clone-url {
416 416 display: inline-block;
417 417 margin: 0 0 .5em 0;
418 418 padding: 0;
419 419 line-height: 1.2em;
420 420 }
421 421 }
422 422
423 423 .pr-mergeinfo {
424 424 min-width: 95% !important;
425 425 padding: 0 !important;
426 426 border: 0;
427 427 }
428 428 .pr-mergeinfo-copy {
429 429 padding: 0 0;
430 430 }
431 431
432 432 .pr-pullinfo {
433 433 min-width: 95% !important;
434 434 padding: 0 !important;
435 435 border: 0;
436 436 }
437 437 .pr-pullinfo-copy {
438 438 padding: 0 0;
439 439 }
440 440
441 441 .pr-title-input {
442 442 width: 100%;
443 443 font-size: 18px;
444 444 margin: 0 0 4px 0;
445 445 padding: 0;
446 446 line-height: 1.7em;
447 447 color: @text-color;
448 448 letter-spacing: .02em;
449 449 font-weight: @text-bold-weight;
450 450 font-family: @text-bold;
451 451
452 452 &:hover {
453 453 box-shadow: none;
454 454 }
455 455 }
456 456
457 457 #pr-title {
458 458 input {
459 459 border: 1px transparent;
460 460 color: black;
461 461 opacity: 1;
462 462 background: #fff;
463 463 font-size: 18px;
464 464 }
465 465 }
466 466
467 467 .pr-title-closed-tag {
468 468 font-size: 16px;
469 469 }
470 470
471 471 #pr-desc {
472 472 padding: 10px 0;
473 473
474 474 .markdown-block {
475 475 padding: 0;
476 476 margin-bottom: -30px;
477 477 }
478 478 }
479 479
480 480 #pullrequest_title {
481 481 width: 100%;
482 482 box-sizing: border-box;
483 483 }
484 484
485 485 #pr_open_message {
486 486 border: @border-thickness solid #fff;
487 487 border-radius: @border-radius;
488 488 text-align: left;
489 489 overflow: hidden;
490 490 white-space: pre-line;
491 491 padding-top: 5px
492 492 }
493 493
494 494 #add_reviewer {
495 495 padding-top: 10px;
496 496 }
497 497
498 #add_reviewer_input {
498 #add_reviewer_input,
499 #add_observer_input {
499 500 padding-top: 10px
500 501 }
501 502
502 503 .pr-details-title-author-pref {
503 504 padding-right: 10px
504 505 }
505 506
506 507 .label-pr-detail {
507 508 display: table-cell;
508 509 width: 120px;
509 510 padding-top: 7.5px;
510 511 padding-bottom: 7.5px;
511 512 padding-right: 7.5px;
512 513 }
513 514
514 515 .source-details ul {
515 516 padding: 10px 16px;
516 517 }
517 518
518 519 .source-details-action {
519 520 color: @grey4;
520 521 font-size: 11px
521 522 }
522 523
523 524 .pr-submit-button {
524 525 float: right;
525 526 margin: 0 0 0 5px;
526 527 }
527 528
528 529 .pr-spacing-container {
529 530 padding: 20px;
530 531 clear: both
531 532 }
532 533
533 534 #pr-description-input {
534 535 margin-bottom: 0;
535 536 }
536 537
537 538 .pr-description-label {
538 539 vertical-align: top;
539 540 }
540 541
541 542 #open_edit_pullrequest {
542 543 padding: 0;
543 544 }
544 545
545 546 #close_edit_pullrequest {
546 547
547 548 }
548 549
549 550 #delete_pullrequest {
550 551 clear: inherit;
551 552
552 553 form {
553 554 display: inline;
554 555 }
555 556
556 557 }
557 558
558 559 .perms_section_head {
559 560 min-width: 625px;
560 561
561 562 h2 {
562 563 margin-bottom: 0;
563 564 }
564 565
565 566 .label-checkbox {
566 567 float: left;
567 568 }
568 569
569 570 &.field {
570 571 margin: @space 0 @padding;
571 572 }
572 573
573 574 &:first-child.field {
574 575 margin-top: 0;
575 576
576 577 .label {
577 578 margin-top: 0;
578 579 padding-top: 0;
579 580 }
580 581
581 582 .radios {
582 583 padding-top: 0;
583 584 }
584 585 }
585 586
586 587 .radios {
587 588 position: relative;
588 589 width: 505px;
589 590 }
590 591 }
591 592
592 593 //--- MODULES ------------------//
593 594
594 595
595 596 // Server Announcement
596 597 #server-announcement {
597 598 width: 95%;
598 599 margin: @padding auto;
599 600 padding: @padding;
600 601 border-width: 2px;
601 602 border-style: solid;
602 603 .border-radius(2px);
603 604 font-weight: @text-bold-weight;
604 605 font-family: @text-bold;
605 606
606 607 &.info { border-color: @alert4; background-color: @alert4-inner; }
607 608 &.warning { border-color: @alert3; background-color: @alert3-inner; }
608 609 &.error { border-color: @alert2; background-color: @alert2-inner; }
609 610 &.success { border-color: @alert1; background-color: @alert1-inner; }
610 611 &.neutral { border-color: @grey3; background-color: @grey6; }
611 612 }
612 613
613 614 // Fixed Sidebar Column
614 615 .sidebar-col-wrapper {
615 616 padding-left: @sidebar-all-width;
616 617
617 618 .sidebar {
618 619 width: @sidebar-width;
619 620 margin-left: -@sidebar-all-width;
620 621 }
621 622 }
622 623
623 624 .sidebar-col-wrapper.scw-small {
624 625 padding-left: @sidebar-small-all-width;
625 626
626 627 .sidebar {
627 628 width: @sidebar-small-width;
628 629 margin-left: -@sidebar-small-all-width;
629 630 }
630 631 }
631 632
632 633
633 634 // FOOTER
634 635 #footer {
635 636 padding: 0;
636 637 text-align: center;
637 638 vertical-align: middle;
638 639 color: @grey2;
639 640 font-size: 11px;
640 641
641 642 p {
642 643 margin: 0;
643 644 padding: 1em;
644 645 line-height: 1em;
645 646 }
646 647
647 648 .server-instance { //server instance
648 649 display: none;
649 650 }
650 651
651 652 .title {
652 653 float: none;
653 654 margin: 0 auto;
654 655 }
655 656 }
656 657
657 658 button.close {
658 659 padding: 0;
659 660 cursor: pointer;
660 661 background: transparent;
661 662 border: 0;
662 663 .box-shadow(none);
663 664 -webkit-appearance: none;
664 665 }
665 666
666 667 .close {
667 668 float: right;
668 669 font-size: 21px;
669 670 font-family: @text-bootstrap;
670 671 line-height: 1em;
671 672 font-weight: bold;
672 673 color: @grey2;
673 674
674 675 &:hover,
675 676 &:focus {
676 677 color: @grey1;
677 678 text-decoration: none;
678 679 cursor: pointer;
679 680 }
680 681 }
681 682
682 683 // GRID
683 684 .sorting,
684 685 .sorting_desc,
685 686 .sorting_asc {
686 687 cursor: pointer;
687 688 }
688 689 .sorting_desc:after {
689 690 content: "\00A0\25B2";
690 691 font-size: .75em;
691 692 }
692 693 .sorting_asc:after {
693 694 content: "\00A0\25BC";
694 695 font-size: .68em;
695 696 }
696 697
697 698
698 699 .user_auth_tokens {
699 700
700 701 &.truncate {
701 702 white-space: nowrap;
702 703 overflow: hidden;
703 704 text-overflow: ellipsis;
704 705 }
705 706
706 707 .fields .field .input {
707 708 margin: 0;
708 709 }
709 710
710 711 input#description {
711 712 width: 100px;
712 713 margin: 0;
713 714 }
714 715
715 716 .drop-menu {
716 717 // TODO: johbo: Remove this, should work out of the box when
717 718 // having multiple inputs inline
718 719 margin: 0 0 0 5px;
719 720 }
720 721 }
721 722 #user_list_table {
722 723 .closed {
723 724 background-color: @grey6;
724 725 }
725 726 }
726 727
727 728
728 729 input, textarea {
729 730 &.disabled {
730 731 opacity: .5;
731 732 }
732 733
733 734 &:hover {
734 735 border-color: @grey3;
735 736 box-shadow: @button-shadow;
736 737 }
737 738
738 739 &:focus {
739 740 border-color: @rcblue;
740 741 box-shadow: @button-shadow;
741 742 }
742 743 }
743 744
744 745 // remove extra padding in firefox
745 746 input::-moz-focus-inner { border:0; padding:0 }
746 747
747 748 .adjacent input {
748 749 margin-bottom: @padding;
749 750 }
750 751
751 752 .permissions_boxes {
752 753 display: block;
753 754 }
754 755
755 756 //FORMS
756 757
757 758 .medium-inline,
758 759 input#description.medium-inline {
759 760 display: inline;
760 761 width: @medium-inline-input-width;
761 762 min-width: 100px;
762 763 }
763 764
764 765 select {
765 766 //reset
766 767 -webkit-appearance: none;
767 768 -moz-appearance: none;
768 769
769 770 display: inline-block;
770 771 height: 28px;
771 772 width: auto;
772 773 margin: 0 @padding @padding 0;
773 774 padding: 0 18px 0 8px;
774 775 line-height:1em;
775 776 font-size: @basefontsize;
776 777 border: @border-thickness solid @grey5;
777 778 border-radius: @border-radius;
778 779 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
779 780 color: @grey4;
780 781 box-shadow: @button-shadow;
781 782
782 783 &:after {
783 784 content: "\00A0\25BE";
784 785 }
785 786
786 787 &:focus, &:hover {
787 788 outline: none;
788 789 border-color: @grey4;
789 790 color: @rcdarkblue;
790 791 }
791 792 }
792 793
793 794 option {
794 795 &:focus {
795 796 outline: none;
796 797 }
797 798 }
798 799
799 800 input,
800 801 textarea {
801 802 padding: @input-padding;
802 803 border: @input-border-thickness solid @border-highlight-color;
803 804 .border-radius (@border-radius);
804 805 font-family: @text-light;
805 806 font-size: @basefontsize;
806 807
807 808 &.input-sm {
808 809 padding: 5px;
809 810 }
810 811
811 812 &#description {
812 813 min-width: @input-description-minwidth;
813 814 min-height: 1em;
814 815 padding: 10px;
815 816 }
816 817 }
817 818
818 819 .field-sm {
819 820 input,
820 821 textarea {
821 822 padding: 5px;
822 823 }
823 824 }
824 825
825 826 textarea {
826 827 display: block;
827 828 clear: both;
828 829 width: 100%;
829 830 min-height: 100px;
830 831 margin-bottom: @padding;
831 832 .box-sizing(border-box);
832 833 overflow: auto;
833 834 }
834 835
835 836 label {
836 837 font-family: @text-light;
837 838 }
838 839
839 840 // GRAVATARS
840 841 // centers gravatar on username to the right
841 842
842 843 .gravatar {
843 844 display: inline;
844 845 min-width: 16px;
845 846 min-height: 16px;
846 847 margin: -5px 0;
847 848 padding: 0;
848 849 line-height: 1em;
849 850 box-sizing: content-box;
850 851 border-radius: 50%;
851 852
852 853 &.gravatar-large {
853 854 margin: -0.5em .25em -0.5em 0;
854 855 }
855 856
856 857 & + .user {
857 858 display: inline;
858 859 margin: 0;
859 860 padding: 0 0 0 .17em;
860 861 line-height: 1em;
861 862 }
862 863
863 864 & + .no-margin {
864 865 margin: 0
865 866 }
866 867
867 868 }
868 869
869 870 .user-inline-data {
870 871 display: inline-block;
871 872 float: left;
872 873 padding-left: .5em;
873 874 line-height: 1.3em;
874 875 }
875 876
876 877 .rc-user { // gravatar + user wrapper
877 878 float: left;
878 879 position: relative;
879 880 min-width: 100px;
880 881 max-width: 200px;
881 882 min-height: (@gravatar-size + @border-thickness * 2); // account for border
882 883 display: block;
883 884 padding: 0 0 0 (@gravatar-size + @basefontsize/4);
884 885
885 886
886 887 .gravatar {
887 888 display: block;
888 889 position: absolute;
889 890 top: 0;
890 891 left: 0;
891 892 min-width: @gravatar-size;
892 893 min-height: @gravatar-size;
893 894 margin: 0;
894 895 }
895 896
896 897 .user {
897 898 display: block;
898 899 max-width: 175px;
899 900 padding-top: 2px;
900 901 overflow: hidden;
901 902 text-overflow: ellipsis;
902 903 }
903 904 }
904 905
905 906 .gist-gravatar,
906 907 .journal_container {
907 908 .gravatar-large {
908 909 margin: 0 .5em -10px 0;
909 910 }
910 911 }
911 912
912 913 .gist-type-fields {
913 914 line-height: 30px;
914 915 height: 30px;
915 916
916 917 .gist-type-fields-wrapper {
917 918 vertical-align: middle;
918 919 display: inline-block;
919 920 line-height: 25px;
920 921 }
921 922 }
922 923
923 924 // ADMIN SETTINGS
924 925
925 926 // Tag Patterns
926 927 .tag_patterns {
927 928 .tag_input {
928 929 margin-bottom: @padding;
929 930 }
930 931 }
931 932
932 933 .locked_input {
933 934 position: relative;
934 935
935 936 input {
936 937 display: inline;
937 938 margin: 3px 5px 0px 0px;
938 939 }
939 940
940 941 br {
941 942 display: none;
942 943 }
943 944
944 945 .error-message {
945 946 float: left;
946 947 width: 100%;
947 948 }
948 949
949 950 .lock_input_button {
950 951 display: inline;
951 952 }
952 953
953 954 .help-block {
954 955 clear: both;
955 956 }
956 957 }
957 958
958 959 // Notifications
959 960
960 961 .notifications_buttons {
961 962 margin: 0 0 @space 0;
962 963 padding: 0;
963 964
964 965 .btn {
965 966 display: inline-block;
966 967 }
967 968 }
968 969
969 970 .notification-list {
970 971
971 972 div {
972 973 vertical-align: middle;
973 974 }
974 975
975 976 .container {
976 977 display: block;
977 978 margin: 0 0 @padding 0;
978 979 }
979 980
980 981 .delete-notifications {
981 982 margin-left: @padding;
982 983 text-align: right;
983 984 cursor: pointer;
984 985 }
985 986
986 987 .read-notifications {
987 988 margin-left: @padding/2;
988 989 text-align: right;
989 990 width: 35px;
990 991 cursor: pointer;
991 992 }
992 993
993 994 .icon-minus-sign {
994 995 color: @alert2;
995 996 }
996 997
997 998 .icon-ok-sign {
998 999 color: @alert1;
999 1000 }
1000 1001 }
1001 1002
1002 1003 .user_settings {
1003 1004 float: left;
1004 1005 clear: both;
1005 1006 display: block;
1006 1007 width: 100%;
1007 1008
1008 1009 .gravatar_box {
1009 1010 margin-bottom: @padding;
1010 1011
1011 1012 &:after {
1012 1013 content: " ";
1013 1014 clear: both;
1014 1015 width: 100%;
1015 1016 }
1016 1017 }
1017 1018
1018 1019 .fields .field {
1019 1020 clear: both;
1020 1021 }
1021 1022 }
1022 1023
1023 1024 .advanced_settings {
1024 1025 margin-bottom: @space;
1025 1026
1026 1027 .help-block {
1027 1028 margin-left: 0;
1028 1029 }
1029 1030
1030 1031 button + .help-block {
1031 1032 margin-top: @padding;
1032 1033 }
1033 1034 }
1034 1035
1035 1036 // admin settings radio buttons and labels
1036 1037 .label-2 {
1037 1038 float: left;
1038 1039 width: @label2-width;
1039 1040
1040 1041 label {
1041 1042 color: @grey1;
1042 1043 }
1043 1044 }
1044 1045 .checkboxes {
1045 1046 float: left;
1046 1047 width: @checkboxes-width;
1047 1048 margin-bottom: @padding;
1048 1049
1049 1050 .checkbox {
1050 1051 width: 100%;
1051 1052
1052 1053 label {
1053 1054 margin: 0;
1054 1055 padding: 0;
1055 1056 }
1056 1057 }
1057 1058
1058 1059 .checkbox + .checkbox {
1059 1060 display: inline-block;
1060 1061 }
1061 1062
1062 1063 label {
1063 1064 margin-right: 1em;
1064 1065 }
1065 1066 }
1066 1067
1067 1068 // CHANGELOG
1068 1069 .container_header {
1069 1070 float: left;
1070 1071 display: block;
1071 1072 width: 100%;
1072 1073 margin: @padding 0 @padding;
1073 1074
1074 1075 #filter_changelog {
1075 1076 float: left;
1076 1077 margin-right: @padding;
1077 1078 }
1078 1079
1079 1080 .breadcrumbs_light {
1080 1081 display: inline-block;
1081 1082 }
1082 1083 }
1083 1084
1084 1085 .info_box {
1085 1086 float: right;
1086 1087 }
1087 1088
1088 1089
1089 1090
1090 1091 #graph_content{
1091 1092
1092 1093 // adjust for table headers so that graph renders properly
1093 1094 // #graph_nodes padding - table cell padding
1094 1095 padding-top: (@space - (@basefontsize * 2.4));
1095 1096
1096 1097 &.graph_full_width {
1097 1098 width: 100%;
1098 1099 max-width: 100%;
1099 1100 }
1100 1101 }
1101 1102
1102 1103 #graph {
1103 1104
1104 1105 .pagination-left {
1105 1106 float: left;
1106 1107 clear: both;
1107 1108 }
1108 1109
1109 1110 .log-container {
1110 1111 max-width: 345px;
1111 1112
1112 1113 .message{
1113 1114 max-width: 340px;
1114 1115 }
1115 1116 }
1116 1117
1117 1118 .graph-col-wrapper {
1118 1119
1119 1120 #graph_nodes {
1120 1121 width: 100px;
1121 1122 position: absolute;
1122 1123 left: 70px;
1123 1124 z-index: -1;
1124 1125 }
1125 1126 }
1126 1127
1127 1128 .load-more-commits {
1128 1129 text-align: center;
1129 1130 }
1130 1131 .load-more-commits:hover {
1131 1132 background-color: @grey7;
1132 1133 }
1133 1134 .load-more-commits {
1134 1135 a {
1135 1136 display: block;
1136 1137 }
1137 1138 }
1138 1139 }
1139 1140
1140 1141 .obsolete-toggle {
1141 1142 line-height: 30px;
1142 1143 margin-left: -15px;
1143 1144 }
1144 1145
1145 1146 #rev_range_container, #rev_range_clear, #rev_range_more {
1146 1147 margin-top: -5px;
1147 1148 margin-bottom: -5px;
1148 1149 }
1149 1150
1150 1151 #filter_changelog {
1151 1152 float: left;
1152 1153 }
1153 1154
1154 1155
1155 1156 //--- THEME ------------------//
1156 1157
1157 1158 #logo {
1158 1159 float: left;
1159 1160 margin: 9px 0 0 0;
1160 1161
1161 1162 .header {
1162 1163 background-color: transparent;
1163 1164 }
1164 1165
1165 1166 a {
1166 1167 display: inline-block;
1167 1168 }
1168 1169
1169 1170 img {
1170 1171 height:30px;
1171 1172 }
1172 1173 }
1173 1174
1174 1175 .logo-wrapper {
1175 1176 float:left;
1176 1177 }
1177 1178
1178 1179 .branding {
1179 1180 float: left;
1180 1181 padding: 9px 2px;
1181 1182 line-height: 1em;
1182 1183 font-size: @navigation-fontsize;
1183 1184
1184 1185 a {
1185 1186 color: @grey5
1186 1187 }
1187 1188
1188 1189 // 1024px or smaller
1189 1190 @media screen and (max-width: 1180px) {
1190 1191 display: none;
1191 1192 }
1192 1193
1193 1194 }
1194 1195
1195 1196 img {
1196 1197 border: none;
1197 1198 outline: none;
1198 1199 }
1199 1200 user-profile-header
1200 1201 label {
1201 1202
1202 1203 input[type="checkbox"] {
1203 1204 margin-right: 1em;
1204 1205 }
1205 1206 input[type="radio"] {
1206 1207 margin-right: 1em;
1207 1208 }
1208 1209 }
1209 1210
1210 1211 .review-status {
1211 1212 &.under_review {
1212 1213 color: @alert3;
1213 1214 }
1214 1215 &.approved {
1215 1216 color: @alert1;
1216 1217 }
1217 1218 &.rejected,
1218 1219 &.forced_closed{
1219 1220 color: @alert2;
1220 1221 }
1221 1222 &.not_reviewed {
1222 1223 color: @grey5;
1223 1224 }
1224 1225 }
1225 1226
1226 1227 .review-status-under_review {
1227 1228 color: @alert3;
1228 1229 }
1229 1230 .status-tag-under_review {
1230 1231 border-color: @alert3;
1231 1232 }
1232 1233
1233 1234 .review-status-approved {
1234 1235 color: @alert1;
1235 1236 }
1236 1237 .status-tag-approved {
1237 1238 border-color: @alert1;
1238 1239 }
1239 1240
1240 1241 .review-status-rejected,
1241 1242 .review-status-forced_closed {
1242 1243 color: @alert2;
1243 1244 }
1244 1245 .status-tag-rejected,
1245 1246 .status-tag-forced_closed {
1246 1247 border-color: @alert2;
1247 1248 }
1248 1249
1249 1250 .review-status-not_reviewed {
1250 1251 color: @grey5;
1251 1252 }
1252 1253 .status-tag-not_reviewed {
1253 1254 border-color: @grey5;
1254 1255 }
1255 1256
1256 1257 .test_pattern_preview {
1257 1258 margin: @space 0;
1258 1259
1259 1260 p {
1260 1261 margin-bottom: 0;
1261 1262 border-bottom: @border-thickness solid @border-default-color;
1262 1263 color: @grey3;
1263 1264 }
1264 1265
1265 1266 .btn {
1266 1267 margin-bottom: @padding;
1267 1268 }
1268 1269 }
1269 1270 #test_pattern_result {
1270 1271 display: none;
1271 1272 &:extend(pre);
1272 1273 padding: .9em;
1273 1274 color: @grey3;
1274 1275 background-color: @grey7;
1275 1276 border-right: @border-thickness solid @border-default-color;
1276 1277 border-bottom: @border-thickness solid @border-default-color;
1277 1278 border-left: @border-thickness solid @border-default-color;
1278 1279 }
1279 1280
1280 1281 #repo_vcs_settings {
1281 1282 #inherit_overlay_vcs_default {
1282 1283 display: none;
1283 1284 }
1284 1285 #inherit_overlay_vcs_custom {
1285 1286 display: custom;
1286 1287 }
1287 1288 &.inherited {
1288 1289 #inherit_overlay_vcs_default {
1289 1290 display: block;
1290 1291 }
1291 1292 #inherit_overlay_vcs_custom {
1292 1293 display: none;
1293 1294 }
1294 1295 }
1295 1296 }
1296 1297
1297 1298 .issue-tracker-link {
1298 1299 color: @rcblue;
1299 1300 }
1300 1301
1301 1302 // Issue Tracker Table Show/Hide
1302 1303 #repo_issue_tracker {
1303 1304 #inherit_overlay {
1304 1305 display: none;
1305 1306 }
1306 1307 #custom_overlay {
1307 1308 display: custom;
1308 1309 }
1309 1310 &.inherited {
1310 1311 #inherit_overlay {
1311 1312 display: block;
1312 1313 }
1313 1314 #custom_overlay {
1314 1315 display: none;
1315 1316 }
1316 1317 }
1317 1318 }
1318 1319 table.issuetracker {
1319 1320 &.readonly {
1320 1321 tr, td {
1321 1322 color: @grey3;
1322 1323 }
1323 1324 }
1324 1325 .edit {
1325 1326 display: none;
1326 1327 }
1327 1328 .editopen {
1328 1329 .edit {
1329 1330 display: inline;
1330 1331 }
1331 1332 .entry {
1332 1333 display: none;
1333 1334 }
1334 1335 }
1335 1336 tr td.td-action {
1336 1337 min-width: 117px;
1337 1338 }
1338 1339 td input {
1339 1340 max-width: none;
1340 1341 min-width: 30px;
1341 1342 width: 80%;
1342 1343 }
1343 1344 .issuetracker_pref input {
1344 1345 width: 40%;
1345 1346 }
1346 1347 input.edit_issuetracker_update {
1347 1348 margin-right: 0;
1348 1349 width: auto;
1349 1350 }
1350 1351 }
1351 1352
1352 1353 table.integrations {
1353 1354 .td-icon {
1354 1355 width: 20px;
1355 1356 .integration-icon {
1356 1357 height: 20px;
1357 1358 width: 20px;
1358 1359 }
1359 1360 }
1360 1361 }
1361 1362
1362 1363 .integrations {
1363 1364 a.integration-box {
1364 1365 color: @text-color;
1365 1366 &:hover {
1366 1367 .panel {
1367 1368 background: #fbfbfb;
1368 1369 }
1369 1370 }
1370 1371 .integration-icon {
1371 1372 width: 30px;
1372 1373 height: 30px;
1373 1374 margin-right: 20px;
1374 1375 float: left;
1375 1376 }
1376 1377
1377 1378 .panel-body {
1378 1379 padding: 10px;
1379 1380 }
1380 1381 .panel {
1381 1382 margin-bottom: 10px;
1382 1383 }
1383 1384 h2 {
1384 1385 display: inline-block;
1385 1386 margin: 0;
1386 1387 min-width: 140px;
1387 1388 }
1388 1389 }
1389 1390 a.integration-box.dummy-integration {
1390 1391 color: @grey4
1391 1392 }
1392 1393 }
1393 1394
1394 1395 //Permissions Settings
1395 1396 #add_perm {
1396 1397 margin: 0 0 @padding;
1397 1398 cursor: pointer;
1398 1399 }
1399 1400
1400 1401 .perm_ac {
1401 1402 input {
1402 1403 width: 95%;
1403 1404 }
1404 1405 }
1405 1406
1406 1407 .autocomplete-suggestions {
1407 1408 width: auto !important; // overrides autocomplete.js
1408 1409 min-width: 278px;
1409 1410 margin: 0;
1410 1411 border: @border-thickness solid @grey5;
1411 1412 border-radius: @border-radius;
1412 1413 color: @grey2;
1413 1414 background-color: white;
1414 1415 }
1415 1416
1416 1417 .autocomplete-qfilter-suggestions {
1417 1418 width: auto !important; // overrides autocomplete.js
1418 1419 max-height: 100% !important;
1419 1420 min-width: 376px;
1420 1421 margin: 0;
1421 1422 border: @border-thickness solid @grey5;
1422 1423 color: @grey2;
1423 1424 background-color: white;
1424 1425 }
1425 1426
1426 1427 .autocomplete-selected {
1427 1428 background: #F0F0F0;
1428 1429 }
1429 1430
1430 1431 .ac-container-wrap {
1431 1432 margin: 0;
1432 1433 padding: 8px;
1433 1434 border-bottom: @border-thickness solid @grey5;
1434 1435 list-style-type: none;
1435 1436 cursor: pointer;
1436 1437
1437 1438 &:hover {
1438 1439 background-color: @grey7;
1439 1440 }
1440 1441
1441 1442 img {
1442 1443 height: @gravatar-size;
1443 1444 width: @gravatar-size;
1444 1445 margin-right: 1em;
1445 1446 }
1446 1447
1447 1448 strong {
1448 1449 font-weight: normal;
1449 1450 }
1450 1451 }
1451 1452
1452 1453 // Settings Dropdown
1453 1454 .user-menu .container {
1454 1455 padding: 0 4px;
1455 1456 margin: 0;
1456 1457 }
1457 1458
1458 1459 .user-menu .gravatar {
1459 1460 cursor: pointer;
1460 1461 }
1461 1462
1462 1463 .codeblock {
1463 1464 margin-bottom: @padding;
1464 1465 clear: both;
1465 1466
1466 1467 .stats {
1467 1468 overflow: hidden;
1468 1469 }
1469 1470
1470 1471 .message{
1471 1472 textarea{
1472 1473 margin: 0;
1473 1474 }
1474 1475 }
1475 1476
1476 1477 .code-header {
1477 1478 .stats {
1478 1479 line-height: 2em;
1479 1480
1480 1481 .revision_id {
1481 1482 margin-left: 0;
1482 1483 }
1483 1484 .buttons {
1484 1485 padding-right: 0;
1485 1486 }
1486 1487 }
1487 1488
1488 1489 .item{
1489 1490 margin-right: 0.5em;
1490 1491 }
1491 1492 }
1492 1493
1493 1494 #editor_container {
1494 1495 position: relative;
1495 1496 margin: @padding 10px;
1496 1497 }
1497 1498 }
1498 1499
1499 1500 #file_history_container {
1500 1501 display: none;
1501 1502 }
1502 1503
1503 1504 .file-history-inner {
1504 1505 margin-bottom: 10px;
1505 1506 }
1506 1507
1507 1508 // Pull Requests
1508 1509 .summary-details {
1509 1510 width: 100%;
1510 1511 }
1511 1512 .pr-summary {
1512 1513 border-bottom: @border-thickness solid @grey5;
1513 1514 margin-bottom: @space;
1514 1515 }
1515 1516
1516 1517 .reviewers {
1517 1518 width: 98%;
1518 1519 }
1519 1520
1520 1521 .reviewers ul li {
1521 1522 position: relative;
1522 1523 width: 100%;
1523 1524 padding-bottom: 8px;
1524 1525 list-style-type: none;
1525 1526 }
1526 1527
1527 1528 .reviewer_entry {
1528 1529 min-height: 55px;
1529 1530 }
1530 1531
1531 1532 .reviewer_reason {
1532 1533 padding-left: 20px;
1533 1534 line-height: 1.5em;
1534 1535 }
1535 1536 .reviewer_status {
1536 1537 display: inline-block;
1537 1538 width: 20px;
1538 1539 min-width: 20px;
1539 1540 height: 1.2em;
1540 1541 line-height: 1em;
1541 1542 }
1542 1543
1543 1544 .reviewer_name {
1544 1545 display: inline-block;
1545 1546 max-width: 83%;
1546 1547 padding-right: 20px;
1547 1548 vertical-align: middle;
1548 1549 line-height: 1;
1549 1550
1550 1551 .rc-user {
1551 1552 min-width: 0;
1552 1553 margin: -2px 1em 0 0;
1553 1554 }
1554 1555
1555 1556 .reviewer {
1556 1557 float: left;
1557 1558 }
1558 1559 }
1559 1560
1560 1561 .reviewer_member_mandatory {
1561 1562 width: 16px;
1562 1563 font-size: 11px;
1563 1564 margin: 0;
1564 1565 padding: 0;
1565 1566 color: black;
1566 1567 opacity: 0.4;
1567 1568 }
1568 1569
1569 1570 .reviewer_member_mandatory_remove,
1570 1571 .reviewer_member_remove {
1571 1572 width: 16px;
1572 1573 padding: 0;
1573 1574 color: black;
1574 1575 cursor: pointer;
1575 1576 }
1576 1577
1577 1578 .reviewer_member_mandatory_remove {
1578 1579 color: @grey4;
1579 1580 }
1580 1581
1581 1582 .reviewer_member_status {
1582 1583 margin-top: 5px;
1583 1584 }
1584 1585 .pr-summary #summary{
1585 1586 width: 100%;
1586 1587 }
1587 1588 .pr-summary .action_button:hover {
1588 1589 border: 0;
1589 1590 cursor: pointer;
1590 1591 }
1591 1592 .pr-details-title {
1592 1593 height: 20px;
1593 1594 line-height: 20px;
1594 1595
1595 1596 padding-bottom: 8px;
1596 1597 border-bottom: @border-thickness solid @grey5;
1597 1598
1598 1599 .action_button.disabled {
1599 1600 color: @grey4;
1600 1601 cursor: inherit;
1601 1602 }
1602 1603 .action_button {
1603 1604 color: @rcblue;
1604 1605 }
1605 1606 }
1606 1607 .pr-details-content {
1607 1608 margin-top: @textmargin - 5;
1608 1609 margin-bottom: @textmargin - 5;
1609 1610 }
1610 1611
1611 1612 .pr-reviewer-rules {
1612 1613 padding: 10px 0px 20px 0px;
1613 1614 }
1614 1615
1615 1616 .todo-resolved {
1616 1617 text-decoration: line-through;
1617 1618 }
1618 1619
1619 1620 .todo-table, .comments-table {
1620 1621 width: 100%;
1621 1622
1622 1623 td {
1623 1624 padding: 5px 0px;
1624 1625 }
1625 1626
1626 1627 .td-todo-number {
1627 1628 text-align: left;
1628 1629 white-space: nowrap;
1629 1630 width: 1%;
1630 1631 padding-right: 2px;
1631 1632 }
1632 1633
1633 1634 .td-todo-gravatar {
1634 1635 width: 5%;
1635 1636
1636 1637 img {
1637 1638 margin: -3px 0;
1638 1639 }
1639 1640 }
1640 1641
1641 1642 }
1642 1643
1643 1644 .todo-comment-text-wrapper {
1644 1645 display: inline-grid;
1645 1646 }
1646 1647
1647 1648 .todo-comment-text {
1648 1649 margin-left: 5px;
1649 1650 white-space: nowrap;
1650 1651 overflow: hidden;
1651 1652 text-overflow: ellipsis;
1652 1653 }
1653 1654
1654 1655 table.group_members {
1655 1656 width: 100%
1656 1657 }
1657 1658
1658 1659 .group_members {
1659 1660 margin-top: 0;
1660 1661 padding: 0;
1661 1662
1662 1663 img {
1663 1664 height: @gravatar-size;
1664 1665 width: @gravatar-size;
1665 1666 margin-right: .5em;
1666 1667 margin-left: 3px;
1667 1668 }
1668 1669
1669 1670 .to-delete {
1670 1671 .user {
1671 1672 text-decoration: line-through;
1672 1673 }
1673 1674 }
1674 1675 }
1675 1676
1676 1677 .compare_view_commits_title {
1677 1678 .disabled {
1678 1679 cursor: inherit;
1679 1680 &:hover{
1680 1681 background-color: inherit;
1681 1682 color: inherit;
1682 1683 }
1683 1684 }
1684 1685 }
1685 1686
1686 1687 .subtitle-compare {
1687 1688 margin: -15px 0px 0px 0px;
1688 1689 }
1689 1690
1690 1691 // new entry in group_members
1691 1692 .td-author-new-entry {
1692 1693 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1693 1694 }
1694 1695
1695 1696 .usergroup_member_remove {
1696 1697 width: 16px;
1697 1698 margin-bottom: 10px;
1698 1699 padding: 0;
1699 1700 color: black !important;
1700 1701 cursor: pointer;
1701 1702 }
1702 1703
1703 1704 .reviewer_ac .ac-input {
1704 1705 width: 98%;
1705 1706 margin-bottom: 1em;
1706 1707 }
1707 1708
1708 1709 .observer_ac .ac-input {
1709 1710 width: 98%;
1710 1711 margin-bottom: 1em;
1711 1712 }
1712 1713
1713 1714 .rule-table {
1714 1715 width: 100%;
1715 1716 }
1716 1717
1717 1718 .rule-table td {
1718 1719
1719 1720 }
1720 1721
1721 1722 .rule-table .td-role {
1722 1723 width: 100px
1723 1724 }
1724 1725
1725 1726 .rule-table .td-mandatory {
1726 1727 width: 100px
1727 1728 }
1728 1729
1729 1730 .rule-table .td-group-votes {
1730 1731 width: 150px
1731 1732 }
1732 1733
1733 1734 .compare_view_commits tr{
1734 1735 height: 20px;
1735 1736 }
1736 1737 .compare_view_commits td {
1737 1738 vertical-align: top;
1738 1739 padding-top: 10px;
1739 1740 }
1740 1741 .compare_view_commits .author {
1741 1742 margin-left: 5px;
1742 1743 }
1743 1744
1744 1745 .compare_view_commits {
1745 1746 .color-a {
1746 1747 color: @alert1;
1747 1748 }
1748 1749
1749 1750 .color-c {
1750 1751 color: @color3;
1751 1752 }
1752 1753
1753 1754 .color-r {
1754 1755 color: @color5;
1755 1756 }
1756 1757
1757 1758 .color-a-bg {
1758 1759 background-color: @alert1;
1759 1760 }
1760 1761
1761 1762 .color-c-bg {
1762 1763 background-color: @alert3;
1763 1764 }
1764 1765
1765 1766 .color-r-bg {
1766 1767 background-color: @alert2;
1767 1768 }
1768 1769
1769 1770 .color-a-border {
1770 1771 border: 1px solid @alert1;
1771 1772 }
1772 1773
1773 1774 .color-c-border {
1774 1775 border: 1px solid @alert3;
1775 1776 }
1776 1777
1777 1778 .color-r-border {
1778 1779 border: 1px solid @alert2;
1779 1780 }
1780 1781
1781 1782 .commit-change-indicator {
1782 1783 width: 15px;
1783 1784 height: 15px;
1784 1785 position: relative;
1785 1786 left: 15px;
1786 1787 }
1787 1788
1788 1789 .commit-change-content {
1789 1790 text-align: center;
1790 1791 vertical-align: middle;
1791 1792 line-height: 15px;
1792 1793 }
1793 1794 }
1794 1795
1795 1796 .compare_view_filepath {
1796 1797 color: @grey1;
1797 1798 }
1798 1799
1799 1800 .show_more {
1800 1801 display: inline-block;
1801 1802 width: 0;
1802 1803 height: 0;
1803 1804 vertical-align: middle;
1804 1805 content: "";
1805 1806 border: 4px solid;
1806 1807 border-right-color: transparent;
1807 1808 border-bottom-color: transparent;
1808 1809 border-left-color: transparent;
1809 1810 font-size: 0;
1810 1811 }
1811 1812
1812 1813 .journal_more .show_more {
1813 1814 display: inline;
1814 1815
1815 1816 &:after {
1816 1817 content: none;
1817 1818 }
1818 1819 }
1819 1820
1820 1821 .compare_view_commits .collapse_commit:after {
1821 1822 cursor: pointer;
1822 1823 content: "\00A0\25B4";
1823 1824 margin-left: -3px;
1824 1825 font-size: 17px;
1825 1826 color: @grey4;
1826 1827 }
1827 1828
1828 1829 .diff_links {
1829 1830 margin-left: 8px;
1830 1831 }
1831 1832
1832 1833 #pull_request_overview {
1833 1834 div.ancestor {
1834 1835 margin: -33px 0;
1835 1836 }
1836 1837 }
1837 1838
1838 1839 div.ancestor {
1839 1840
1840 1841 }
1841 1842
1842 1843 .cs_icon_td input[type="checkbox"] {
1843 1844 display: none;
1844 1845 }
1845 1846
1846 1847 .cs_icon_td .expand_file_icon:after {
1847 1848 cursor: pointer;
1848 1849 content: "\00A0\25B6";
1849 1850 font-size: 12px;
1850 1851 color: @grey4;
1851 1852 }
1852 1853
1853 1854 .cs_icon_td .collapse_file_icon:after {
1854 1855 cursor: pointer;
1855 1856 content: "\00A0\25BC";
1856 1857 font-size: 12px;
1857 1858 color: @grey4;
1858 1859 }
1859 1860
1860 1861 /*new binary
1861 1862 NEW_FILENODE = 1
1862 1863 DEL_FILENODE = 2
1863 1864 MOD_FILENODE = 3
1864 1865 RENAMED_FILENODE = 4
1865 1866 COPIED_FILENODE = 5
1866 1867 CHMOD_FILENODE = 6
1867 1868 BIN_FILENODE = 7
1868 1869 */
1869 1870 .cs_files_expand {
1870 1871 font-size: @basefontsize + 5px;
1871 1872 line-height: 1.8em;
1872 1873 float: right;
1873 1874 }
1874 1875
1875 1876 .cs_files_expand span{
1876 1877 color: @rcblue;
1877 1878 cursor: pointer;
1878 1879 }
1879 1880 .cs_files {
1880 1881 clear: both;
1881 1882 padding-bottom: @padding;
1882 1883
1883 1884 .cur_cs {
1884 1885 margin: 10px 2px;
1885 1886 font-weight: bold;
1886 1887 }
1887 1888
1888 1889 .node {
1889 1890 float: left;
1890 1891 }
1891 1892
1892 1893 .changes {
1893 1894 float: right;
1894 1895 color: white;
1895 1896 font-size: @basefontsize - 4px;
1896 1897 margin-top: 4px;
1897 1898 opacity: 0.6;
1898 1899 filter: Alpha(opacity=60); /* IE8 and earlier */
1899 1900
1900 1901 .added {
1901 1902 background-color: @alert1;
1902 1903 float: left;
1903 1904 text-align: center;
1904 1905 }
1905 1906
1906 1907 .deleted {
1907 1908 background-color: @alert2;
1908 1909 float: left;
1909 1910 text-align: center;
1910 1911 }
1911 1912
1912 1913 .bin {
1913 1914 background-color: @alert1;
1914 1915 text-align: center;
1915 1916 }
1916 1917
1917 1918 /*new binary*/
1918 1919 .bin.bin1 {
1919 1920 background-color: @alert1;
1920 1921 text-align: center;
1921 1922 }
1922 1923
1923 1924 /*deleted binary*/
1924 1925 .bin.bin2 {
1925 1926 background-color: @alert2;
1926 1927 text-align: center;
1927 1928 }
1928 1929
1929 1930 /*mod binary*/
1930 1931 .bin.bin3 {
1931 1932 background-color: @grey2;
1932 1933 text-align: center;
1933 1934 }
1934 1935
1935 1936 /*rename file*/
1936 1937 .bin.bin4 {
1937 1938 background-color: @alert4;
1938 1939 text-align: center;
1939 1940 }
1940 1941
1941 1942 /*copied file*/
1942 1943 .bin.bin5 {
1943 1944 background-color: @alert4;
1944 1945 text-align: center;
1945 1946 }
1946 1947
1947 1948 /*chmod file*/
1948 1949 .bin.bin6 {
1949 1950 background-color: @grey2;
1950 1951 text-align: center;
1951 1952 }
1952 1953 }
1953 1954 }
1954 1955
1955 1956 .cs_files .cs_added, .cs_files .cs_A,
1956 1957 .cs_files .cs_added, .cs_files .cs_M,
1957 1958 .cs_files .cs_added, .cs_files .cs_D {
1958 1959 height: 16px;
1959 1960 padding-right: 10px;
1960 1961 margin-top: 7px;
1961 1962 text-align: left;
1962 1963 }
1963 1964
1964 1965 .cs_icon_td {
1965 1966 min-width: 16px;
1966 1967 width: 16px;
1967 1968 }
1968 1969
1969 1970 .pull-request-merge {
1970 1971 border: 1px solid @grey5;
1971 1972 padding: 10px 0px 20px;
1972 1973 margin-top: 10px;
1973 1974 margin-bottom: 20px;
1974 1975 }
1975 1976
1976 1977 .pull-request-merge-refresh {
1977 1978 margin: 2px 7px;
1978 1979 a {
1979 1980 color: @grey3;
1980 1981 }
1981 1982 }
1982 1983
1983 1984 .pull-request-merge ul {
1984 1985 padding: 0px 0px;
1985 1986 }
1986 1987
1987 1988 .pull-request-merge li {
1988 1989 list-style-type: none;
1989 1990 }
1990 1991
1991 1992 .pull-request-merge .pull-request-wrap {
1992 1993 height: auto;
1993 1994 padding: 0px 0px;
1994 1995 text-align: right;
1995 1996 }
1996 1997
1997 1998 .pull-request-merge span {
1998 1999 margin-right: 5px;
1999 2000 }
2000 2001
2001 2002 .pull-request-merge-actions {
2002 2003 min-height: 30px;
2003 2004 padding: 0px 0px;
2004 2005 }
2005 2006
2006 2007 .pull-request-merge-info {
2007 2008 padding: 0px 5px 5px 0px;
2008 2009 }
2009 2010
2010 2011 .merge-status {
2011 2012 margin-right: 5px;
2012 2013 }
2013 2014
2014 2015 .merge-message {
2015 2016 font-size: 1.2em
2016 2017 }
2017 2018
2018 2019 .merge-message.success i,
2019 2020 .merge-icon.success i {
2020 2021 color:@alert1;
2021 2022 }
2022 2023
2023 2024 .merge-message.warning i,
2024 2025 .merge-icon.warning i {
2025 2026 color: @alert3;
2026 2027 }
2027 2028
2028 2029 .merge-message.error i,
2029 2030 .merge-icon.error i {
2030 2031 color:@alert2;
2031 2032 }
2032 2033
2033 2034 .pr-versions {
2034 2035 font-size: 1.1em;
2035 2036 padding: 7.5px;
2036 2037
2037 2038 table {
2038 2039
2039 2040 }
2040 2041
2041 2042 td {
2042 2043 line-height: 15px;
2043 2044 }
2044 2045
2045 2046 .compare-radio-button {
2046 2047 position: relative;
2047 2048 top: -3px;
2048 2049 }
2049 2050 }
2050 2051
2051 2052
2052 2053 #close_pull_request {
2053 2054 margin-right: 0px;
2054 2055 }
2055 2056
2056 2057 .empty_data {
2057 2058 color: @grey4;
2058 2059 }
2059 2060
2060 2061 #changeset_compare_view_content {
2061 2062 clear: both;
2062 2063 width: 100%;
2063 2064 box-sizing: border-box;
2064 2065 .border-radius(@border-radius);
2065 2066
2066 2067 .help-block {
2067 2068 margin: @padding 0;
2068 2069 color: @text-color;
2069 2070 &.pre-formatting {
2070 2071 white-space: pre;
2071 2072 }
2072 2073 }
2073 2074
2074 2075 .empty_data {
2075 2076 margin: @padding 0;
2076 2077 }
2077 2078
2078 2079 .alert {
2079 2080 margin-bottom: @space;
2080 2081 }
2081 2082 }
2082 2083
2083 2084 .table_disp {
2084 2085 .status {
2085 2086 width: auto;
2086 2087 }
2087 2088 }
2088 2089
2089 2090
2090 2091 .creation_in_progress {
2091 2092 color: @grey4
2092 2093 }
2093 2094
2094 2095 .status_box_menu {
2095 2096 margin: 0;
2096 2097 }
2097 2098
2098 2099 .notification-table{
2099 2100 margin-bottom: @space;
2100 2101 display: table;
2101 2102 width: 100%;
2102 2103
2103 2104 .container{
2104 2105 display: table-row;
2105 2106
2106 2107 .notification-header{
2107 2108 border-bottom: @border-thickness solid @border-default-color;
2108 2109 }
2109 2110
2110 2111 .notification-subject{
2111 2112 display: table-cell;
2112 2113 }
2113 2114 }
2114 2115 }
2115 2116
2116 2117 // Notifications
2117 2118 .notification-header{
2118 2119 display: table;
2119 2120 width: 100%;
2120 2121 padding: floor(@basefontsize/2) 0;
2121 2122 line-height: 1em;
2122 2123
2123 2124 .desc, .delete-notifications, .read-notifications{
2124 2125 display: table-cell;
2125 2126 text-align: left;
2126 2127 }
2127 2128
2128 2129 .delete-notifications, .read-notifications{
2129 2130 width: 35px;
2130 2131 min-width: 35px; //fixes when only one button is displayed
2131 2132 }
2132 2133 }
2133 2134
2134 2135 .notification-body {
2135 2136 .markdown-block,
2136 2137 .rst-block {
2137 2138 padding: @padding 0;
2138 2139 }
2139 2140
2140 2141 .notification-subject {
2141 2142 padding: @textmargin 0;
2142 2143 border-bottom: @border-thickness solid @border-default-color;
2143 2144 }
2144 2145 }
2145 2146
2146 2147 .notice-messages {
2147 2148 .markdown-block,
2148 2149 .rst-block {
2149 2150 padding: 0;
2150 2151 }
2151 2152 }
2152 2153
2153 2154 .notifications_buttons{
2154 2155 float: right;
2155 2156 }
2156 2157
2157 2158 #notification-status{
2158 2159 display: inline;
2159 2160 }
2160 2161
2161 2162 // Repositories
2162 2163
2163 2164 #summary.fields{
2164 2165 display: table;
2165 2166
2166 2167 .field{
2167 2168 display: table-row;
2168 2169
2169 2170 .label-summary{
2170 2171 display: table-cell;
2171 2172 min-width: @label-summary-minwidth;
2172 2173 padding-top: @padding/2;
2173 2174 padding-bottom: @padding/2;
2174 2175 padding-right: @padding/2;
2175 2176 }
2176 2177
2177 2178 .input{
2178 2179 display: table-cell;
2179 2180 padding: @padding/2;
2180 2181
2181 2182 input{
2182 2183 min-width: 29em;
2183 2184 padding: @padding/4;
2184 2185 }
2185 2186 }
2186 2187 .statistics, .downloads{
2187 2188 .disabled{
2188 2189 color: @grey4;
2189 2190 }
2190 2191 }
2191 2192 }
2192 2193 }
2193 2194
2194 2195 #summary{
2195 2196 width: 70%;
2196 2197 }
2197 2198
2198 2199
2199 2200 // Journal
2200 2201 .journal.title {
2201 2202 h5 {
2202 2203 float: left;
2203 2204 margin: 0;
2204 2205 width: 70%;
2205 2206 }
2206 2207
2207 2208 ul {
2208 2209 float: right;
2209 2210 display: inline-block;
2210 2211 margin: 0;
2211 2212 width: 30%;
2212 2213 text-align: right;
2213 2214
2214 2215 li {
2215 2216 display: inline;
2216 2217 font-size: @journal-fontsize;
2217 2218 line-height: 1em;
2218 2219
2219 2220 list-style-type: none;
2220 2221 }
2221 2222 }
2222 2223 }
2223 2224
2224 2225 .filterexample {
2225 2226 position: absolute;
2226 2227 top: 95px;
2227 2228 left: @contentpadding;
2228 2229 color: @rcblue;
2229 2230 font-size: 11px;
2230 2231 font-family: @text-regular;
2231 2232 cursor: help;
2232 2233
2233 2234 &:hover {
2234 2235 color: @rcdarkblue;
2235 2236 }
2236 2237
2237 2238 @media (max-width:768px) {
2238 2239 position: relative;
2239 2240 top: auto;
2240 2241 left: auto;
2241 2242 display: block;
2242 2243 }
2243 2244 }
2244 2245
2245 2246
2246 2247 #journal{
2247 2248 margin-bottom: @space;
2248 2249
2249 2250 .journal_day{
2250 2251 margin-bottom: @textmargin/2;
2251 2252 padding-bottom: @textmargin/2;
2252 2253 font-size: @journal-fontsize;
2253 2254 border-bottom: @border-thickness solid @border-default-color;
2254 2255 }
2255 2256
2256 2257 .journal_container{
2257 2258 margin-bottom: @space;
2258 2259
2259 2260 .journal_user{
2260 2261 display: inline-block;
2261 2262 }
2262 2263 .journal_action_container{
2263 2264 display: block;
2264 2265 margin-top: @textmargin;
2265 2266
2266 2267 div{
2267 2268 display: inline;
2268 2269 }
2269 2270
2270 2271 div.journal_action_params{
2271 2272 display: block;
2272 2273 }
2273 2274
2274 2275 div.journal_repo:after{
2275 2276 content: "\A";
2276 2277 white-space: pre;
2277 2278 }
2278 2279
2279 2280 div.date{
2280 2281 display: block;
2281 2282 margin-bottom: @textmargin;
2282 2283 }
2283 2284 }
2284 2285 }
2285 2286 }
2286 2287
2287 2288 // Files
2288 2289 .edit-file-title {
2289 2290 font-size: 16px;
2290 2291
2291 2292 .title-heading {
2292 2293 padding: 2px;
2293 2294 }
2294 2295 }
2295 2296
2296 2297 .edit-file-fieldset {
2297 2298 margin: @sidebarpadding 0;
2298 2299
2299 2300 .fieldset {
2300 2301 .left-label {
2301 2302 width: 13%;
2302 2303 }
2303 2304 .right-content {
2304 2305 width: 87%;
2305 2306 max-width: 100%;
2306 2307 }
2307 2308 .filename-label {
2308 2309 margin-top: 13px;
2309 2310 }
2310 2311 .commit-message-label {
2311 2312 margin-top: 4px;
2312 2313 }
2313 2314 .file-upload-input {
2314 2315 input {
2315 2316 display: none;
2316 2317 }
2317 2318 margin-top: 10px;
2318 2319 }
2319 2320 .file-upload-label {
2320 2321 margin-top: 10px;
2321 2322 }
2322 2323 p {
2323 2324 margin-top: 5px;
2324 2325 }
2325 2326
2326 2327 }
2327 2328 .custom-path-link {
2328 2329 margin-left: 5px;
2329 2330 }
2330 2331 #commit {
2331 2332 resize: vertical;
2332 2333 }
2333 2334 }
2334 2335
2335 2336 .delete-file-preview {
2336 2337 max-height: 250px;
2337 2338 }
2338 2339
2339 2340 .new-file,
2340 2341 #filter_activate,
2341 2342 #filter_deactivate {
2342 2343 float: right;
2343 2344 margin: 0 0 0 10px;
2344 2345 }
2345 2346
2346 2347 .file-upload-transaction-wrapper {
2347 2348 margin-top: 57px;
2348 2349 clear: both;
2349 2350 }
2350 2351
2351 2352 .file-upload-transaction-wrapper .error {
2352 2353 color: @color5;
2353 2354 }
2354 2355
2355 2356 .file-upload-transaction {
2356 2357 min-height: 200px;
2357 2358 padding: 54px;
2358 2359 border: 1px solid @grey5;
2359 2360 text-align: center;
2360 2361 clear: both;
2361 2362 }
2362 2363
2363 2364 .file-upload-transaction i {
2364 2365 font-size: 48px
2365 2366 }
2366 2367
2367 2368 h3.files_location{
2368 2369 line-height: 2.4em;
2369 2370 }
2370 2371
2371 2372 .browser-nav {
2372 2373 width: 100%;
2373 2374 display: table;
2374 2375 margin-bottom: 20px;
2375 2376
2376 2377 .info_box {
2377 2378 float: left;
2378 2379 display: inline-table;
2379 2380 height: 2.5em;
2380 2381
2381 2382 .browser-cur-rev, .info_box_elem {
2382 2383 display: table-cell;
2383 2384 vertical-align: middle;
2384 2385 }
2385 2386
2386 2387 .drop-menu {
2387 2388 margin: 0 10px;
2388 2389 }
2389 2390
2390 2391 .info_box_elem {
2391 2392 border-top: @border-thickness solid @grey5;
2392 2393 border-bottom: @border-thickness solid @grey5;
2393 2394 box-shadow: @button-shadow;
2394 2395
2395 2396 #at_rev, a {
2396 2397 padding: 0.6em 0.4em;
2397 2398 margin: 0;
2398 2399 .box-shadow(none);
2399 2400 border: 0;
2400 2401 height: 12px;
2401 2402 color: @grey2;
2402 2403 }
2403 2404
2404 2405 input#at_rev {
2405 2406 max-width: 50px;
2406 2407 text-align: center;
2407 2408 }
2408 2409
2409 2410 &.previous {
2410 2411 border: @border-thickness solid @grey5;
2411 2412 border-top-left-radius: @border-radius;
2412 2413 border-bottom-left-radius: @border-radius;
2413 2414
2414 2415 &:hover {
2415 2416 border-color: @grey4;
2416 2417 }
2417 2418
2418 2419 .disabled {
2419 2420 color: @grey5;
2420 2421 cursor: not-allowed;
2421 2422 opacity: 0.5;
2422 2423 }
2423 2424 }
2424 2425
2425 2426 &.next {
2426 2427 border: @border-thickness solid @grey5;
2427 2428 border-top-right-radius: @border-radius;
2428 2429 border-bottom-right-radius: @border-radius;
2429 2430
2430 2431 &:hover {
2431 2432 border-color: @grey4;
2432 2433 }
2433 2434
2434 2435 .disabled {
2435 2436 color: @grey5;
2436 2437 cursor: not-allowed;
2437 2438 opacity: 0.5;
2438 2439 }
2439 2440 }
2440 2441 }
2441 2442
2442 2443 .browser-cur-rev {
2443 2444
2444 2445 span{
2445 2446 margin: 0;
2446 2447 color: @rcblue;
2447 2448 height: 12px;
2448 2449 display: inline-block;
2449 2450 padding: 0.7em 1em ;
2450 2451 border: @border-thickness solid @rcblue;
2451 2452 margin-right: @padding;
2452 2453 }
2453 2454 }
2454 2455
2455 2456 }
2456 2457
2457 2458 .select-index-number {
2458 2459 margin: 0 0 0 20px;
2459 2460 color: @grey3;
2460 2461 }
2461 2462
2462 2463 .search_activate {
2463 2464 display: table-cell;
2464 2465 vertical-align: middle;
2465 2466
2466 2467 input, label{
2467 2468 margin: 0;
2468 2469 padding: 0;
2469 2470 }
2470 2471
2471 2472 input{
2472 2473 margin-left: @textmargin;
2473 2474 }
2474 2475
2475 2476 }
2476 2477 }
2477 2478
2478 2479 .browser-cur-rev{
2479 2480 margin-bottom: @textmargin;
2480 2481 }
2481 2482
2482 2483 #node_filter_box_loading{
2483 2484 .info_text;
2484 2485 }
2485 2486
2486 2487 .browser-search {
2487 2488 margin: -25px 0px 5px 0px;
2488 2489 }
2489 2490
2490 2491 .files-quick-filter {
2491 2492 float: right;
2492 2493 width: 180px;
2493 2494 position: relative;
2494 2495 }
2495 2496
2496 2497 .files-filter-box {
2497 2498 display: flex;
2498 2499 padding: 0px;
2499 2500 border-radius: 3px;
2500 2501 margin-bottom: 0;
2501 2502
2502 2503 a {
2503 2504 border: none !important;
2504 2505 }
2505 2506
2506 2507 li {
2507 2508 list-style-type: none
2508 2509 }
2509 2510 }
2510 2511
2511 2512 .files-filter-box-path {
2512 2513 line-height: 33px;
2513 2514 padding: 0;
2514 2515 width: 20px;
2515 2516 position: absolute;
2516 2517 z-index: 11;
2517 2518 left: 5px;
2518 2519 }
2519 2520
2520 2521 .files-filter-box-input {
2521 2522 margin-right: 0;
2522 2523
2523 2524 input {
2524 2525 border: 1px solid @white;
2525 2526 padding-left: 25px;
2526 2527 width: 145px;
2527 2528
2528 2529 &:hover {
2529 2530 border-color: @grey6;
2530 2531 }
2531 2532
2532 2533 &:focus {
2533 2534 border-color: @grey5;
2534 2535 }
2535 2536 }
2536 2537 }
2537 2538
2538 2539 .browser-result{
2539 2540 td a{
2540 2541 margin-left: 0.5em;
2541 2542 display: inline-block;
2542 2543
2543 2544 em {
2544 2545 font-weight: @text-bold-weight;
2545 2546 font-family: @text-bold;
2546 2547 }
2547 2548 }
2548 2549 }
2549 2550
2550 2551 .browser-highlight{
2551 2552 background-color: @grey5-alpha;
2552 2553 }
2553 2554
2554 2555
2555 2556 .edit-file-fieldset #location,
2556 2557 .edit-file-fieldset #filename {
2557 2558 display: flex;
2558 2559 width: -moz-available; /* WebKit-based browsers will ignore this. */
2559 2560 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2560 2561 width: fill-available;
2561 2562 border: 0;
2562 2563 }
2563 2564
2564 2565 .path-items {
2565 2566 display: flex;
2566 2567 padding: 0;
2567 2568 border: 1px solid #eeeeee;
2568 2569 width: 100%;
2569 2570 float: left;
2570 2571
2571 2572 .breadcrumb-path {
2572 2573 line-height: 30px;
2573 2574 padding: 0 4px;
2574 2575 white-space: nowrap;
2575 2576 }
2576 2577
2577 2578 .upload-form {
2578 2579 margin-top: 46px;
2579 2580 }
2580 2581
2581 2582 .location-path {
2582 2583 width: -moz-available; /* WebKit-based browsers will ignore this. */
2583 2584 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2584 2585 width: fill-available;
2585 2586
2586 2587 .file-name-input {
2587 2588 padding: 0.5em 0;
2588 2589 }
2589 2590
2590 2591 }
2591 2592
2592 2593 ul {
2593 2594 display: flex;
2594 2595 margin: 0;
2595 2596 padding: 0;
2596 2597 width: 100%;
2597 2598 }
2598 2599
2599 2600 li {
2600 2601 list-style-type: none;
2601 2602 }
2602 2603
2603 2604 }
2604 2605
2605 2606 .editor-items {
2606 2607 height: 40px;
2607 2608 margin: 10px 0 -17px 10px;
2608 2609
2609 2610 .editor-action {
2610 2611 cursor: pointer;
2611 2612 }
2612 2613
2613 2614 .editor-action.active {
2614 2615 border-bottom: 2px solid #5C5C5C;
2615 2616 }
2616 2617
2617 2618 li {
2618 2619 list-style-type: none;
2619 2620 }
2620 2621 }
2621 2622
2622 2623 .edit-file-fieldset .message textarea {
2623 2624 border: 1px solid #eeeeee;
2624 2625 }
2625 2626
2626 2627 #files_data .codeblock {
2627 2628 background-color: #F5F5F5;
2628 2629 }
2629 2630
2630 2631 #editor_preview {
2631 2632 background: white;
2632 2633 }
2633 2634
2634 2635 .show-editor {
2635 2636 padding: 10px;
2636 2637 background-color: white;
2637 2638
2638 2639 }
2639 2640
2640 2641 .show-preview {
2641 2642 padding: 10px;
2642 2643 background-color: white;
2643 2644 border-left: 1px solid #eeeeee;
2644 2645 }
2645 2646 // quick filter
2646 2647 .grid-quick-filter {
2647 2648 float: right;
2648 2649 position: relative;
2649 2650 }
2650 2651
2651 2652 .grid-filter-box {
2652 2653 display: flex;
2653 2654 padding: 0px;
2654 2655 border-radius: 3px;
2655 2656 margin-bottom: 0;
2656 2657
2657 2658 a {
2658 2659 border: none !important;
2659 2660 }
2660 2661
2661 2662 li {
2662 2663 list-style-type: none
2663 2664 }
2664 2665
2665 2666 }
2666 2667
2667 2668 .grid-filter-box-icon {
2668 2669 line-height: 33px;
2669 2670 padding: 0;
2670 2671 width: 20px;
2671 2672 position: absolute;
2672 2673 z-index: 11;
2673 2674 left: 5px;
2674 2675 }
2675 2676
2676 2677 .grid-filter-box-input {
2677 2678 margin-right: 0;
2678 2679
2679 2680 input {
2680 2681 border: 1px solid @white;
2681 2682 padding-left: 25px;
2682 2683 width: 145px;
2683 2684
2684 2685 &:hover {
2685 2686 border-color: @grey6;
2686 2687 }
2687 2688
2688 2689 &:focus {
2689 2690 border-color: @grey5;
2690 2691 }
2691 2692 }
2692 2693 }
2693 2694
2694 2695
2695 2696
2696 2697 // Search
2697 2698
2698 2699 .search-form{
2699 2700 #q {
2700 2701 width: @search-form-width;
2701 2702 }
2702 2703 .fields{
2703 2704 margin: 0 0 @space;
2704 2705 }
2705 2706
2706 2707 label{
2707 2708 display: inline-block;
2708 2709 margin-right: @textmargin;
2709 2710 padding-top: 0.25em;
2710 2711 }
2711 2712
2712 2713
2713 2714 .results{
2714 2715 clear: both;
2715 2716 margin: 0 0 @padding;
2716 2717 }
2717 2718
2718 2719 .search-tags {
2719 2720 padding: 5px 0;
2720 2721 }
2721 2722 }
2722 2723
2723 2724 div.search-feedback-items {
2724 2725 display: inline-block;
2725 2726 }
2726 2727
2727 2728 div.search-code-body {
2728 2729 background-color: #ffffff; padding: 5px 0 5px 10px;
2729 2730 pre {
2730 2731 .match { background-color: #faffa6;}
2731 2732 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2732 2733 }
2733 2734 }
2734 2735
2735 2736 .expand_commit.search {
2736 2737 .show_more.open {
2737 2738 height: auto;
2738 2739 max-height: none;
2739 2740 }
2740 2741 }
2741 2742
2742 2743 .search-results {
2743 2744
2744 2745 h2 {
2745 2746 margin-bottom: 0;
2746 2747 }
2747 2748 .codeblock {
2748 2749 border: none;
2749 2750 background: transparent;
2750 2751 }
2751 2752
2752 2753 .codeblock-header {
2753 2754 border: none;
2754 2755 background: transparent;
2755 2756 }
2756 2757
2757 2758 .code-body {
2758 2759 border: @border-thickness solid @grey6;
2759 2760 .border-radius(@border-radius);
2760 2761 }
2761 2762
2762 2763 .td-commit {
2763 2764 &:extend(pre);
2764 2765 border-bottom: @border-thickness solid @border-default-color;
2765 2766 }
2766 2767
2767 2768 .message {
2768 2769 height: auto;
2769 2770 max-width: 350px;
2770 2771 white-space: normal;
2771 2772 text-overflow: initial;
2772 2773 overflow: visible;
2773 2774
2774 2775 .match { background-color: #faffa6;}
2775 2776 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2776 2777 }
2777 2778
2778 2779 .path {
2779 2780 border-bottom: none !important;
2780 2781 border-left: 1px solid @grey6 !important;
2781 2782 border-right: 1px solid @grey6 !important;
2782 2783 }
2783 2784 }
2784 2785
2785 2786 table.rctable td.td-search-results div {
2786 2787 max-width: 100%;
2787 2788 }
2788 2789
2789 2790 #tip-box, .tip-box{
2790 2791 padding: @menupadding/2;
2791 2792 display: block;
2792 2793 border: @border-thickness solid @border-highlight-color;
2793 2794 .border-radius(@border-radius);
2794 2795 background-color: white;
2795 2796 z-index: 99;
2796 2797 white-space: pre-wrap;
2797 2798 }
2798 2799
2799 2800 #linktt {
2800 2801 width: 79px;
2801 2802 }
2802 2803
2803 2804 #help_kb .modal-content{
2804 2805 max-width: 800px;
2805 2806 margin: 10% auto;
2806 2807
2807 2808 table{
2808 2809 td,th{
2809 2810 border-bottom: none;
2810 2811 line-height: 2.5em;
2811 2812 }
2812 2813 th{
2813 2814 padding-bottom: @textmargin/2;
2814 2815 }
2815 2816 td.keys{
2816 2817 text-align: center;
2817 2818 }
2818 2819 }
2819 2820
2820 2821 .block-left{
2821 2822 width: 45%;
2822 2823 margin-right: 5%;
2823 2824 }
2824 2825 .modal-footer{
2825 2826 clear: both;
2826 2827 }
2827 2828 .key.tag{
2828 2829 padding: 0.5em;
2829 2830 background-color: @rcblue;
2830 2831 color: white;
2831 2832 border-color: @rcblue;
2832 2833 .box-shadow(none);
2833 2834 }
2834 2835 }
2835 2836
2836 2837
2837 2838
2838 2839 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2839 2840
2840 2841 @import 'statistics-graph';
2841 2842 @import 'tables';
2842 2843 @import 'forms';
2843 2844 @import 'diff';
2844 2845 @import 'summary';
2845 2846 @import 'navigation';
2846 2847
2847 2848 //--- SHOW/HIDE SECTIONS --//
2848 2849
2849 2850 .btn-collapse {
2850 2851 float: right;
2851 2852 text-align: right;
2852 2853 font-family: @text-light;
2853 2854 font-size: @basefontsize;
2854 2855 cursor: pointer;
2855 2856 border: none;
2856 2857 color: @rcblue;
2857 2858 }
2858 2859
2859 2860 table.rctable,
2860 2861 table.dataTable {
2861 2862 .btn-collapse {
2862 2863 float: right;
2863 2864 text-align: right;
2864 2865 }
2865 2866 }
2866 2867
2867 2868 table.rctable {
2868 2869 &.permissions {
2869 2870
2870 2871 th.td-owner {
2871 2872 padding: 0;
2872 2873 }
2873 2874
2874 2875 th {
2875 2876 font-weight: normal;
2876 2877 padding: 0 5px;
2877 2878 }
2878 2879
2879 2880 }
2880 2881 }
2881 2882
2882 2883
2883 2884 // TODO: johbo: Fix for IE10, this avoids that we see a border
2884 2885 // and padding around checkboxes and radio boxes. Move to the right place,
2885 2886 // or better: Remove this once we did the form refactoring.
2886 2887 input[type=checkbox],
2887 2888 input[type=radio] {
2888 2889 padding: 0;
2889 2890 border: none;
2890 2891 }
2891 2892
2892 2893 .toggle-ajax-spinner{
2893 2894 height: 16px;
2894 2895 width: 16px;
2895 2896 }
2896 2897
2897 2898
2898 2899 .markup-form .clearfix {
2899 2900 .border-radius(@border-radius);
2900 2901 margin: 0px;
2901 2902 }
2902 2903
2903 2904 .markup-form-area {
2904 2905 padding: 8px 12px;
2905 2906 border: 1px solid @grey4;
2906 2907 .border-radius(@border-radius);
2907 2908 }
2908 2909
2909 2910 .markup-form-area-header .nav-links {
2910 2911 display: flex;
2911 2912 flex-flow: row wrap;
2912 2913 -webkit-flex-flow: row wrap;
2913 2914 width: 100%;
2914 2915 }
2915 2916
2916 2917 .markup-form-area-footer {
2917 2918 display: flex;
2918 2919 }
2919 2920
2920 2921 .markup-form-area-footer .toolbar {
2921 2922
2922 2923 }
2923 2924
2924 2925 // markup Form
2925 2926 div.markup-form {
2926 2927 margin-top: 20px;
2927 2928 }
2928 2929
2929 2930 .markup-form strong {
2930 2931 display: block;
2931 2932 margin-bottom: 15px;
2932 2933 }
2933 2934
2934 2935 .markup-form textarea {
2935 2936 width: 100%;
2936 2937 height: 100px;
2937 2938 font-family: @text-monospace;
2938 2939 }
2939 2940
2940 2941 form.markup-form {
2941 2942 margin-top: 10px;
2942 2943 margin-left: 10px;
2943 2944 }
2944 2945
2945 2946 .markup-form .comment-block-ta,
2946 2947 .markup-form .preview-box {
2947 2948 .border-radius(@border-radius);
2948 2949 .box-sizing(border-box);
2949 2950 background-color: white;
2950 2951 }
2951 2952
2952 2953 .markup-form .preview-box.unloaded {
2953 2954 height: 50px;
2954 2955 text-align: center;
2955 2956 padding: 20px;
2956 2957 background-color: white;
2957 2958 }
2958 2959
2959 2960
2960 2961 .dropzone-wrapper {
2961 2962 border: 1px solid @grey5;
2962 2963 padding: 20px;
2963 2964 }
2964 2965
2965 2966 .dropzone,
2966 2967 .dropzone-pure {
2967 2968 border: 2px dashed @grey5;
2968 2969 border-radius: 5px;
2969 2970 background: white;
2970 2971 min-height: 200px;
2971 2972 padding: 54px;
2972 2973
2973 2974 .dz-message {
2974 2975 font-weight: 700;
2975 2976 text-align: center;
2976 2977 margin: 2em 0;
2977 2978 }
2978 2979
2979 2980 }
2980 2981
2981 2982 .dz-preview {
2982 2983 margin: 10px 0 !important;
2983 2984 position: relative;
2984 2985 vertical-align: top;
2985 2986 padding: 10px;
2986 2987 border-bottom: 1px solid @grey5;
2987 2988 }
2988 2989
2989 2990 .dz-filename {
2990 2991 font-weight: 700;
2991 2992 float: left;
2992 2993 }
2993 2994
2994 2995 .dz-sending {
2995 2996 float: right;
2996 2997 }
2997 2998
2998 2999 .dz-response {
2999 3000 clear: both
3000 3001 }
3001 3002
3002 3003 .dz-filename-size {
3003 3004 float: right
3004 3005 }
3005 3006
3006 3007 .dz-error-message {
3007 3008 color: @alert2;
3008 3009 padding-top: 10px;
3009 3010 clear: both;
3010 3011 }
3011 3012
3012 3013
3013 3014 .user-hovercard {
3014 3015 padding: 5px;
3015 3016 }
3016 3017
3017 3018 .user-hovercard-icon {
3018 3019 display: inline;
3019 3020 padding: 0;
3020 3021 box-sizing: content-box;
3021 3022 border-radius: 50%;
3022 3023 float: left;
3023 3024 }
3024 3025
3025 3026 .user-hovercard-name {
3026 3027 float: right;
3027 3028 vertical-align: top;
3028 3029 padding-left: 10px;
3029 3030 min-width: 150px;
3030 3031 }
3031 3032
3032 3033 .user-hovercard-bio {
3033 3034 clear: both;
3034 3035 padding-top: 10px;
3035 3036 }
3036 3037
3037 3038 .user-hovercard-header {
3038 3039 clear: both;
3039 3040 min-height: 10px;
3040 3041 }
3041 3042
3042 3043 .user-hovercard-footer {
3043 3044 clear: both;
3044 3045 min-height: 10px;
3045 3046 }
3046 3047
3047 3048 .user-group-hovercard {
3048 3049 padding: 5px;
3049 3050 }
3050 3051
3051 3052 .user-group-hovercard-icon {
3052 3053 display: inline;
3053 3054 padding: 0;
3054 3055 box-sizing: content-box;
3055 3056 border-radius: 50%;
3056 3057 float: left;
3057 3058 }
3058 3059
3059 3060 .user-group-hovercard-name {
3060 3061 float: left;
3061 3062 vertical-align: top;
3062 3063 padding-left: 10px;
3063 3064 min-width: 150px;
3064 3065 }
3065 3066
3066 3067 .user-group-hovercard-icon i {
3067 3068 border: 1px solid @grey4;
3068 3069 border-radius: 4px;
3069 3070 }
3070 3071
3071 3072 .user-group-hovercard-bio {
3072 3073 clear: both;
3073 3074 padding-top: 10px;
3074 3075 line-height: 1.0em;
3075 3076 }
3076 3077
3077 3078 .user-group-hovercard-header {
3078 3079 clear: both;
3079 3080 min-height: 10px;
3080 3081 }
3081 3082
3082 3083 .user-group-hovercard-footer {
3083 3084 clear: both;
3084 3085 min-height: 10px;
3085 3086 }
3086 3087
3087 3088 .pr-hovercard-header {
3088 3089 clear: both;
3089 3090 display: block;
3090 3091 line-height: 20px;
3091 3092 }
3092 3093
3093 3094 .pr-hovercard-user {
3094 3095 display: flex;
3095 3096 align-items: center;
3096 3097 padding-left: 5px;
3097 3098 }
3098 3099
3099 3100 .pr-hovercard-title {
3100 3101 padding-top: 5px;
3101 3102 }
3102 3103
3103 3104 .action-divider {
3104 3105 opacity: 0.5;
3105 3106 }
3106 3107
3107 3108 .details-inline-block {
3108 3109 display: inline-block;
3109 3110 position: relative;
3110 3111 }
3111 3112
3112 3113 .details-inline-block summary {
3113 3114 list-style: none;
3114 3115 }
3115 3116
3116 3117 details:not([open]) > :not(summary) {
3117 3118 display: none !important;
3118 3119 }
3119 3120
3120 3121 .details-reset > summary {
3121 3122 list-style: none;
3122 3123 }
3123 3124
3124 3125 .details-reset > summary::-webkit-details-marker {
3125 3126 display: none;
3126 3127 }
3127 3128
3128 3129 .details-dropdown {
3129 3130 position: absolute;
3130 3131 top: 100%;
3131 3132 width: 185px;
3132 3133 list-style: none;
3133 3134 background-color: #fff;
3134 3135 background-clip: padding-box;
3135 3136 border: 1px solid @grey5;
3136 3137 box-shadow: 0 8px 24px rgba(149, 157, 165, .2);
3137 3138 left: -150px;
3138 3139 text-align: left;
3139 3140 z-index: 90;
3140 3141 }
3141 3142
3142 3143 .dropdown-divider {
3143 3144 display: block;
3144 3145 height: 0;
3145 3146 margin: 8px 0;
3146 3147 border-top: 1px solid @grey5;
3147 3148 }
3148 3149
3149 3150 .dropdown-item {
3150 3151 display: block;
3151 3152 padding: 4px 8px 4px 16px;
3152 3153 overflow: hidden;
3153 3154 text-overflow: ellipsis;
3154 3155 white-space: nowrap;
3155 3156 font-weight: normal;
3156 3157 }
3157 3158
3158 3159 .right-sidebar {
3159 3160 position: fixed;
3160 3161 top: 0px;
3161 3162 bottom: 0;
3162 3163 right: 0;
3163 3164
3164 3165 background: #fafafa;
3165 3166 z-index: 50;
3166 3167 }
3167 3168
3168 3169 .right-sidebar {
3169 3170 border-left: 1px solid @grey5;
3170 3171 }
3171 3172
3172 3173 .right-sidebar.right-sidebar-expanded {
3173 3174 width: 300px;
3174 3175 overflow: scroll;
3175 3176 }
3176 3177
3177 3178 .right-sidebar.right-sidebar-collapsed {
3178 3179 width: 40px;
3179 3180 padding: 0;
3180 3181 display: block;
3181 3182 overflow: hidden;
3182 3183 }
3183 3184
3184 3185 .sidenav {
3185 3186 float: right;
3186 3187 will-change: min-height;
3187 3188 background: #fafafa;
3188 3189 width: 100%;
3189 3190 }
3190 3191
3191 3192 .sidebar-toggle {
3192 3193 height: 30px;
3193 3194 text-align: center;
3194 3195 margin: 15px 0px 0 0;
3195 3196 }
3196 3197
3197 3198 .sidebar-toggle a {
3198 3199
3199 3200 }
3200 3201
3201 3202 .sidebar-content {
3202 3203 margin-left: 15px;
3203 3204 margin-right: 15px;
3204 3205 }
3205 3206
3206 3207 .sidebar-heading {
3207 3208 font-size: 1.2em;
3208 3209 font-weight: 700;
3209 3210 margin-top: 10px;
3210 3211 }
3211 3212
3212 3213 .sidebar-element {
3213 3214 margin-top: 20px;
3214 3215 }
3215 3216
3216 3217 .right-sidebar-collapsed-state {
3217 3218 display: flex;
3218 3219 flex-direction: column;
3219 3220 justify-content: center;
3220 3221 align-items: center;
3221 3222 padding: 0 10px;
3222 3223 cursor: pointer;
3223 3224 font-size: 1.3em;
3224 3225 margin: 0 -15px;
3225 3226 }
3226 3227
3227 3228 .right-sidebar-collapsed-state:hover {
3228 3229 background-color: @grey5;
3229 3230 }
3230 3231
3231 3232 .old-comments-marker {
3232 3233 text-align: left;
3233 3234 }
3234 3235
3235 3236 .old-comments-marker td {
3236 3237 padding-top: 15px;
3237 3238 border-bottom: 1px solid @grey5;
3238 3239 }
@@ -1,635 +1,640 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${c.repo_name} ${_('New pull request')}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()"></%def>
9 9
10 10 <%def name="menu_bar_nav()">
11 11 ${self.menu_items(active='repositories')}
12 12 </%def>
13 13
14 14 <%def name="menu_bar_subnav()">
15 15 ${self.repo_menu(active='showpullrequest')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box">
20 20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
21 21
22 22 <div class="box">
23 23
24 24 <div class="summary-details block-left">
25 25
26 26 <div class="form" style="padding-top: 10px">
27 27
28 28 <div class="fields" >
29 29
30 30 ## COMMIT FLOW
31 31 <div class="field">
32 32 <div class="label label-textarea">
33 33 <label for="commit_flow">${_('Commit flow')}:</label>
34 34 </div>
35 35
36 36 <div class="content">
37 37 <div class="flex-container">
38 38 <div style="width: 45%;">
39 39 <div class="panel panel-default source-panel">
40 40 <div class="panel-heading">
41 41 <h3 class="panel-title">${_('Source repository')}</h3>
42 42 </div>
43 43 <div class="panel-body">
44 44 <div style="display:none">${c.rhodecode_db_repo.description}</div>
45 45 ${h.hidden('source_repo')}
46 46 ${h.hidden('source_ref')}
47 47
48 48 <div id="pr_open_message"></div>
49 49 </div>
50 50 </div>
51 51 </div>
52 52
53 53 <div style="width: 90px; text-align: center; padding-top: 30px">
54 54 <div>
55 55 <i class="icon-right" style="font-size: 2.2em"></i>
56 56 </div>
57 57 <div style="position: relative; top: 10px">
58 58 <span class="tag tag">
59 59 <span id="switch_base"></span>
60 60 </span>
61 61 </div>
62 62
63 63 </div>
64 64
65 65 <div style="width: 45%;">
66 66
67 67 <div class="panel panel-default target-panel">
68 68 <div class="panel-heading">
69 69 <h3 class="panel-title">${_('Target repository')}</h3>
70 70 </div>
71 71 <div class="panel-body">
72 72 <div style="display:none" id="target_repo_desc"></div>
73 73 ${h.hidden('target_repo')}
74 74 ${h.hidden('target_ref')}
75 75 <span id="target_ref_loading" style="display: none">
76 76 ${_('Loading refs...')}
77 77 </span>
78 78 </div>
79 79 </div>
80 80
81 81 </div>
82 82 </div>
83 83
84 84 </div>
85 85
86 86 </div>
87 87
88 88 ## TITLE
89 89 <div class="field">
90 90 <div class="label">
91 91 <label for="pullrequest_title">${_('Title')}:</label>
92 92 </div>
93 93 <div class="input">
94 94 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
95 95 </div>
96 96 <p class="help-block">
97 97 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
98 98 </p>
99 99 </div>
100 100
101 101 ## DESC
102 102 <div class="field">
103 103 <div class="label label-textarea">
104 104 <label for="pullrequest_desc">${_('Description')}:</label>
105 105 </div>
106 106 <div class="textarea text-area">
107 107 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
108 108 ${dt.markup_form('pullrequest_desc')}
109 109 </div>
110 110 </div>
111 111
112 112 ## REVIEWERS
113 113 <div class="field">
114 114 <div class="label label-textarea">
115 115 <label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label>
116 116 </div>
117 117 <div class="content">
118 118 ## REVIEW RULES
119 119 <div id="review_rules" style="display: none" class="reviewers-title">
120 120 <div class="pr-details-title">
121 121 ${_('Reviewer rules')}
122 122 </div>
123 123 <div class="pr-reviewer-rules">
124 124 ## review rules will be appended here, by default reviewers logic
125 125 </div>
126 126 </div>
127 127
128 128 ## REVIEWERS / OBSERVERS
129 129 <div class="reviewers-title">
130 130
131 131 <ul class="nav-links clearfix">
132 132
133 133 ## TAB1 MANDATORY REVIEWERS
134 134 <li class="active">
135 135 <a id="reviewers-btn" href="#showReviewers" tabindex="-1">
136 136 Reviewers
137 137 <span id="reviewers-cnt" data-count="0" class="menulink-counter">0</span>
138 138 </a>
139 139 </li>
140 140
141 141 ## TAB2 OBSERVERS
142 142 <li class="">
143 143 <a id="observers-btn" href="#showObservers" tabindex="-1">
144 144 Observers
145 145 <span id="observers-cnt" data-count="0" class="menulink-counter">0</span>
146 146 </a>
147 147 </li>
148 148
149 149 </ul>
150 150
151 151 ## TAB1 MANDATORY REVIEWERS
152 152 <div id="reviewers-container">
153 153 <span class="calculate-reviewers">
154 154 <h4>${_('loading...')}</h4>
155 155 </span>
156 156
157 157 <div id="reviewers" class="pr-details-content reviewers">
158 158 ## members goes here, filled via JS based on initial selection !
159 159 <input type="hidden" name="__start__" value="review_members:sequence">
160 160 <table id="review_members" class="group_members">
161 161 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
162 162 </table>
163 163 <input type="hidden" name="__end__" value="review_members:sequence">
164 164
165 165 <div id="add_reviewer_input" class='ac'>
166 166 <div class="reviewer_ac">
167 167 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
168 168 <div id="reviewers_container"></div>
169 169 </div>
170 170 </div>
171 171
172 172 </div>
173 173 </div>
174 174
175 175 ## TAB2 OBSERVERS
176 176 <div id="observers-container" style="display: none">
177 177 <span class="calculate-reviewers">
178 178 <h4>${_('loading...')}</h4>
179 179 </span>
180
180 % if c.rhodecode_edition_id == 'EE':
181 181 <div id="observers" class="pr-details-content observers">
182 182 ## members goes here, filled via JS based on initial selection !
183 183 <input type="hidden" name="__start__" value="observer_members:sequence">
184 184 <table id="observer_members" class="group_members">
185 185 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
186 186 </table>
187 187 <input type="hidden" name="__end__" value="observer_members:sequence">
188 188
189 189 <div id="add_observer_input" class='ac'>
190 190 <div class="observer_ac">
191 191 ${h.text('observer', class_='ac-input', placeholder=_('Add observer or observer group'))}
192 192 <div id="observers_container"></div>
193 193 </div>
194 194 </div>
195 195 </div>
196
196 % else:
197 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
198 <p>
199 Pull request observers allows adding users who don't need to leave mandatory votes, but need to be aware about certain changes.
200 </p>
201 % endif
197 202 </div>
198 203
199 204 </div>
200 205
201 206 </div>
202 207 </div>
203 208
204 209 ## SUBMIT
205 210 <div class="field">
206 211 <div class="label label-textarea">
207 212 <label for="pullrequest_submit"></label>
208 213 </div>
209 214 <div class="input">
210 215 <div class="pr-submit-button">
211 216 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
212 217 </div>
213 218 </div>
214 219 </div>
215 220 </div>
216 221 </div>
217 222 </div>
218 223
219 224 </div>
220 225
221 226 ${h.end_form()}
222 227 </div>
223 228
224 229 <script type="text/javascript">
225 230 $(function(){
226 231 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
227 232 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
228 233 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
229 234 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
230 235
231 236 var $pullRequestForm = $('#pull_request_form');
232 237 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
233 238 var $sourceRepo = $('#source_repo', $pullRequestForm);
234 239 var $targetRepo = $('#target_repo', $pullRequestForm);
235 240 var $sourceRef = $('#source_ref', $pullRequestForm);
236 241 var $targetRef = $('#target_ref', $pullRequestForm);
237 242
238 243 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
239 244 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
240 245
241 246 var targetRepo = function() { return $targetRepo.eq(0).val() };
242 247 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
243 248
244 249 var calculateContainerWidth = function() {
245 250 var maxWidth = 0;
246 251 var repoSelect2Containers = ['#source_repo', '#target_repo'];
247 252 $.each(repoSelect2Containers, function(idx, value) {
248 253 $(value).select2('container').width('auto');
249 254 var curWidth = $(value).select2('container').width();
250 255 if (maxWidth <= curWidth) {
251 256 maxWidth = curWidth;
252 257 }
253 258 $.each(repoSelect2Containers, function(idx, value) {
254 259 $(value).select2('container').width(maxWidth + 10);
255 260 });
256 261 });
257 262 };
258 263
259 264 var initRefSelection = function(selectedRef) {
260 265 return function(element, callback) {
261 266 // translate our select2 id into a text, it's a mapping to show
262 267 // simple label when selecting by internal ID.
263 268 var id, refData;
264 269 if (selectedRef === undefined || selectedRef === null) {
265 270 id = element.val();
266 271 refData = element.val().split(':');
267 272
268 273 if (refData.length !== 3){
269 274 refData = ["", "", ""]
270 275 }
271 276 } else {
272 277 id = selectedRef;
273 278 refData = selectedRef.split(':');
274 279 }
275 280
276 281 var text = refData[1];
277 282 if (refData[0] === 'rev') {
278 283 text = text.substring(0, 12);
279 284 }
280 285
281 286 var data = {id: id, text: text};
282 287 callback(data);
283 288 };
284 289 };
285 290
286 291 var formatRefSelection = function(data, container, escapeMarkup) {
287 292 var prefix = '';
288 293 var refData = data.id.split(':');
289 294 if (refData[0] === 'branch') {
290 295 prefix = '<i class="icon-branch"></i>';
291 296 }
292 297 else if (refData[0] === 'book') {
293 298 prefix = '<i class="icon-bookmark"></i>';
294 299 }
295 300 else if (refData[0] === 'tag') {
296 301 prefix = '<i class="icon-tag"></i>';
297 302 }
298 303
299 304 var originalOption = data.element;
300 305 return prefix + escapeMarkup(data.text);
301 306 };formatSelection:
302 307
303 308 // custom code mirror
304 309 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
305 310
306 311 var diffDataHandler = function(data) {
307 312
308 313 var commitElements = data['commits'];
309 314 var files = data['files'];
310 315 var added = data['stats'][0]
311 316 var deleted = data['stats'][1]
312 317 var commonAncestorId = data['ancestor'];
313 318 var _sourceRefType = sourceRef()[0];
314 319 var _sourceRefName = sourceRef()[1];
315 320 var prTitleAndDesc = getTitleAndDescription(_sourceRefType, _sourceRefName, commitElements, 5);
316 321
317 322 var title = prTitleAndDesc[0];
318 323 var proposedDescription = prTitleAndDesc[1];
319 324
320 325 var useGeneratedTitle = (
321 326 $('#pullrequest_title').hasClass('autogenerated-title') ||
322 327 $('#pullrequest_title').val() === "");
323 328
324 329 if (title && useGeneratedTitle) {
325 330 // use generated title if we haven't specified our own
326 331 $('#pullrequest_title').val(title);
327 332 $('#pullrequest_title').addClass('autogenerated-title');
328 333
329 334 }
330 335
331 336 var useGeneratedDescription = (
332 337 !codeMirrorInstance._userDefinedValue ||
333 338 codeMirrorInstance.getValue() === "");
334 339
335 340 if (proposedDescription && useGeneratedDescription) {
336 341 // set proposed content, if we haven't defined our own,
337 342 // or we don't have description written
338 343 codeMirrorInstance._userDefinedValue = false; // reset state
339 344 codeMirrorInstance.setValue(proposedDescription);
340 345 }
341 346
342 347 // refresh our codeMirror so events kicks in and it's change aware
343 348 codeMirrorInstance.refresh();
344 349
345 350 var url_data = {
346 351 'repo_name': targetRepo(),
347 352 'target_repo': sourceRepo(),
348 353 'source_ref': targetRef()[2],
349 354 'source_ref_type': 'rev',
350 355 'target_ref': sourceRef()[2],
351 356 'target_ref_type': 'rev',
352 357 'merge': true,
353 358 '_': Date.now() // bypass browser caching
354 359 }; // gather the source/target ref and repo here
355 360 var url = pyroutes.url('repo_compare', url_data);
356 361
357 362 var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId);
358 363 msg += '<input type="hidden" name="__start__" value="revisions:sequence">'
359 364
360 365 $.each(commitElements, function(idx, value) {
361 366 msg += '<input type="hidden" name="revisions" value="{0}">'.format(value["raw_id"]);
362 367 });
363 368
364 369 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
365 370 msg += _ngettext(
366 371 'Compare summary: <strong>{0} commit</strong>',
367 372 'Compare summary: <strong>{0} commits</strong>',
368 373 commitElements.length).format(commitElements.length)
369 374
370 375 msg += '';
371 376 msg += _ngettext(
372 377 '<strong>, and {0} file</strong> changed.',
373 378 '<strong>, and {0} files</strong> changed.',
374 379 files.length).format(files.length)
375 380
376 381 msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted)
377 382
378 383 msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
379 384
380 385 if (commitElements.length) {
381 386 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
382 387 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
383 388 }
384 389 else {
385 390 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
386 391 _gettext('There are no commits to merge.'));
387 392 prButtonLock(true, noCommitsMsg, 'compare');
388 393 }
389 394
390 395 //make both panels equal
391 396 $('.target-panel').height($('.source-panel').height())
392 397 };
393 398
394 399 reviewersController = new ReviewersController();
395 400 reviewersController.diffDataHandler = diffDataHandler;
396 401
397 402 var queryTargetRepo = function(self, query) {
398 403 // cache ALL results if query is empty
399 404 var cacheKey = query.term || '__';
400 405 var cachedData = self.cachedDataSource[cacheKey];
401 406
402 407 if (cachedData) {
403 408 query.callback({results: cachedData.results});
404 409 } else {
405 410 $.ajax({
406 411 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
407 412 data: {query: query.term},
408 413 dataType: 'json',
409 414 type: 'GET',
410 415 success: function(data) {
411 416 self.cachedDataSource[cacheKey] = data;
412 417 query.callback({results: data.results});
413 418 },
414 419 error: function(jqXHR, textStatus, errorThrown) {
415 420 var prefix = "Error while fetching entries.\n"
416 421 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
417 422 ajaxErrorSwal(message);
418 423 }
419 424 });
420 425 }
421 426 };
422 427
423 428 var queryTargetRefs = function(initialData, query) {
424 429 var data = {results: []};
425 430 // filter initialData
426 431 $.each(initialData, function() {
427 432 var section = this.text;
428 433 var children = [];
429 434 $.each(this.children, function() {
430 435 if (query.term.length === 0 ||
431 436 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
432 437 children.push({'id': this.id, 'text': this.text})
433 438 }
434 439 });
435 440 data.results.push({'text': section, 'children': children})
436 441 });
437 442 query.callback({results: data.results});
438 443 };
439 444
440 445 var Select2Box = function(element, overrides) {
441 446 var globalDefaults = {
442 447 dropdownAutoWidth: true,
443 448 containerCssClass: "drop-menu",
444 449 dropdownCssClass: "drop-menu-dropdown"
445 450 };
446 451
447 452 var initSelect2 = function(defaultOptions) {
448 453 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
449 454 element.select2(options);
450 455 };
451 456
452 457 return {
453 458 initRef: function() {
454 459 var defaultOptions = {
455 460 minimumResultsForSearch: 5,
456 461 formatSelection: formatRefSelection
457 462 };
458 463
459 464 initSelect2(defaultOptions);
460 465 },
461 466
462 467 initRepo: function(defaultValue, readOnly) {
463 468 var defaultOptions = {
464 469 initSelection : function (element, callback) {
465 470 var data = {id: defaultValue, text: defaultValue};
466 471 callback(data);
467 472 }
468 473 };
469 474
470 475 initSelect2(defaultOptions);
471 476
472 477 element.select2('val', defaultSourceRepo);
473 478 if (readOnly === true) {
474 479 element.select2('readonly', true);
475 480 }
476 481 }
477 482 };
478 483 };
479 484
480 485 var initTargetRefs = function(refsData, selectedRef) {
481 486
482 487 Select2Box($targetRef, {
483 488 placeholder: "${_('Select commit reference')}",
484 489 query: function(query) {
485 490 queryTargetRefs(refsData, query);
486 491 },
487 492 initSelection : initRefSelection(selectedRef)
488 493 }).initRef();
489 494
490 495 if (!(selectedRef === undefined)) {
491 496 $targetRef.select2('val', selectedRef);
492 497 }
493 498 };
494 499
495 500 var targetRepoChanged = function(repoData) {
496 501 // generate new DESC of target repo displayed next to select
497 502
498 503 $('#target_repo_desc').html(repoData['description']);
499 504
500 505 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
501 506 var title = _gettext('Switch target repository with the source.')
502 507 $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
503 508
504 509 // generate dynamic select2 for refs.
505 510 initTargetRefs(repoData['refs']['select2_refs'],
506 511 repoData['refs']['selected_ref']);
507 512
508 513 };
509 514
510 515 var sourceRefSelect2 = Select2Box($sourceRef, {
511 516 placeholder: "${_('Select commit reference')}",
512 517 query: function(query) {
513 518 var initialData = defaultSourceRepoData['refs']['select2_refs'];
514 519 queryTargetRefs(initialData, query)
515 520 },
516 521 initSelection: initRefSelection()
517 522 });
518 523
519 524 var sourceRepoSelect2 = Select2Box($sourceRepo, {
520 525 query: function(query) {}
521 526 });
522 527
523 528 var targetRepoSelect2 = Select2Box($targetRepo, {
524 529 cachedDataSource: {},
525 530 query: $.debounce(250, function(query) {
526 531 queryTargetRepo(this, query);
527 532 }),
528 533 formatResult: formatRepoResult
529 534 });
530 535
531 536 sourceRefSelect2.initRef();
532 537
533 538 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
534 539
535 540 targetRepoSelect2.initRepo(defaultTargetRepo, false);
536 541
537 542 $sourceRef.on('change', function(e){
538 543 reviewersController.loadDefaultReviewers(
539 544 sourceRepo(), sourceRef(), targetRepo(), targetRef());
540 545 });
541 546
542 547 $targetRef.on('change', function(e){
543 548 reviewersController.loadDefaultReviewers(
544 549 sourceRepo(), sourceRef(), targetRepo(), targetRef());
545 550 });
546 551
547 552 $targetRepo.on('change', function(e){
548 553 var repoName = $(this).val();
549 554 calculateContainerWidth();
550 555 $targetRef.select2('destroy');
551 556 $('#target_ref_loading').show();
552 557
553 558 $.ajax({
554 559 url: pyroutes.url('pullrequest_repo_refs',
555 560 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
556 561 data: {},
557 562 dataType: 'json',
558 563 type: 'GET',
559 564 success: function(data) {
560 565 $('#target_ref_loading').hide();
561 566 targetRepoChanged(data);
562 567 },
563 568 error: function(jqXHR, textStatus, errorThrown) {
564 569 var prefix = "Error while fetching entries.\n"
565 570 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
566 571 ajaxErrorSwal(message);
567 572 }
568 573 })
569 574
570 575 });
571 576
572 577 $pullRequestForm.on('submit', function(e){
573 578 // Flush changes into textarea
574 579 codeMirrorInstance.save();
575 580 prButtonLock(true, null, 'all');
576 581 $pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
577 582 });
578 583
579 584 prButtonLock(true, "${_('Please select source and target')}", 'all');
580 585
581 586 // auto-load on init, the target refs select2
582 587 calculateContainerWidth();
583 588 targetRepoChanged(defaultTargetRepoData);
584 589
585 590 $('#pullrequest_title').on('keyup', function(e){
586 591 $(this).removeClass('autogenerated-title');
587 592 });
588 593
589 594 % if c.default_source_ref:
590 595 // in case we have a pre-selected value, use it now
591 596 $sourceRef.select2('val', '${c.default_source_ref}');
592 597
593 598
594 599 // default reviewers / observers
595 600 reviewersController.loadDefaultReviewers(
596 601 sourceRepo(), sourceRef(), targetRepo(), targetRef());
597 602 % endif
598 603
599 604 ReviewerAutoComplete('#user', reviewersController);
600 605 ObserverAutoComplete('#observer', reviewersController);
601 606
602 607 // TODO, move this to another handler
603 608
604 609 var $reviewersBtn = $('#reviewers-btn');
605 610 var $reviewersContainer = $('#reviewers-container');
606 611
607 612 var $observersBtn = $('#observers-btn')
608 613 var $observersContainer = $('#observers-container');
609 614
610 615 $reviewersBtn.on('click', function (e) {
611 616
612 617 $observersContainer.hide();
613 618 $reviewersContainer.show();
614 619
615 620 $observersBtn.parent().removeClass('active');
616 621 $reviewersBtn.parent().addClass('active');
617 622 e.preventDefault();
618 623
619 624 })
620 625
621 626 $observersBtn.on('click', function (e) {
622 627
623 628 $reviewersContainer.hide();
624 629 $observersContainer.show();
625 630
626 631 $reviewersBtn.parent().removeClass('active');
627 632 $observersBtn.parent().addClass('active');
628 633 e.preventDefault();
629 634
630 635 })
631 636
632 637 });
633 638 </script>
634 639
635 640 </%def>
@@ -1,999 +1,1001 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5 5
6 6
7 7 <%def name="title()">
8 8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='showpullrequest')}
24 24 </%def>
25 25
26 26
27 27 <%def name="main()">
28 28 ## Container to gather extracted Tickets
29 29 <%
30 30 c.referenced_commit_issues = []
31 31 c.referenced_desc_issues = []
32 32 %>
33 33
34 34 <script type="text/javascript">
35 35 // TODO: marcink switch this to pyroutes
36 36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
39 39 </script>
40 40
41 41 <div class="box">
42 42
43 43 <div class="box pr-summary">
44 44
45 45 <div class="summary-details block-left">
46 46 <div id="pr-title">
47 47 % if c.pull_request.is_closed():
48 48 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
49 49 % endif
50 50 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
51 51 </div>
52 52 <div id="pr-title-edit" class="input" style="display: none;">
53 53 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
54 54 </div>
55 55
56 56 <% summary = lambda n:{False:'summary-short'}.get(n) %>
57 57 <div class="pr-details-title">
58 58 <div class="pull-left">
59 59 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
60 60 ${_('Created on')}
61 61 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
62 62 <span class="pr-details-title-author-pref">${_('by')}</span>
63 63 </div>
64 64
65 65 <div class="pull-left">
66 66 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
67 67 </div>
68 68
69 69 %if c.allowed_to_update:
70 70 <div class="pull-right">
71 71 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
72 72 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
73 73 % if c.allowed_to_delete:
74 74 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
75 75 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
76 76 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
77 77 type="submit" value="${_('Delete pull request')}">
78 78 ${h.end_form()}
79 79 % else:
80 80 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
81 81 % endif
82 82 </div>
83 83 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
84 84 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
85 85 </div>
86 86
87 87 %endif
88 88 </div>
89 89
90 90 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
91 91 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
92 92 </div>
93 93
94 94 <div id="pr-desc-edit" class="input textarea" style="display: none;">
95 95 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
96 96 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
97 97 </div>
98 98
99 99 <div id="summary" class="fields pr-details-content">
100 100
101 101 ## source
102 102 <div class="field">
103 103 <div class="label-pr-detail">
104 104 <label>${_('Commit flow')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 <div class="pr-commit-flow">
108 108 ## Source
109 109 %if c.pull_request.source_ref_parts.type == 'branch':
110 110 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
111 111 %else:
112 112 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
113 113 %endif
114 114 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
115 115 &rarr;
116 116 ## Target
117 117 %if c.pull_request.target_ref_parts.type == 'branch':
118 118 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
119 119 %else:
120 120 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
121 121 %endif
122 122
123 123 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
124 124
125 125 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
126 126 <i class="icon-angle-down">more details</i>
127 127 </a>
128 128
129 129 </div>
130 130
131 131 <div class="source-details" style="display: none">
132 132
133 133 <ul>
134 134
135 135 ## common ancestor
136 136 <li>
137 137 ${_('Common ancestor')}:
138 138 % if c.ancestor_commit:
139 139 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
140 140 % else:
141 141 ${_('not available')}
142 142 % endif
143 143 </li>
144 144
145 145 ## pull url
146 146 <li>
147 147 %if h.is_hg(c.pull_request.source_repo):
148 148 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
149 149 %elif h.is_git(c.pull_request.source_repo):
150 150 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
151 151 %endif
152 152
153 153 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
154 154 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
155 155 </li>
156 156
157 157 ## Shadow repo
158 158 <li>
159 159 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
160 160 %if h.is_hg(c.pull_request.target_repo):
161 161 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
162 162 %elif h.is_git(c.pull_request.target_repo):
163 163 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
164 164 %endif
165 165
166 166 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
167 167 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
168 168
169 169 % else:
170 170 <div class="">
171 171 ${_('Shadow repository data not available')}.
172 172 </div>
173 173 % endif
174 174 </li>
175 175
176 176 </ul>
177 177
178 178 </div>
179 179
180 180 </div>
181 181
182 182 </div>
183 183
184 184 ## versions
185 185 <div class="field">
186 186 <div class="label-pr-detail">
187 187 <label>${_('Versions')}:</label>
188 188 </div>
189 189
190 190 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
191 191 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
192 192
193 193 <div class="pr-versions">
194 194 % if c.show_version_changes:
195 195 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
196 196 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
197 197 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
198 198 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
199 199 data-toggle-on="${_('show versions')}."
200 200 data-toggle-off="${_('hide versions')}.">
201 201 ${_('show versions')}.
202 202 </a>
203 203 <table>
204 204 ## SHOW ALL VERSIONS OF PR
205 205 <% ver_pr = None %>
206 206
207 207 % for data in reversed(list(enumerate(c.versions, 1))):
208 208 <% ver_pos = data[0] %>
209 209 <% ver = data[1] %>
210 210 <% ver_pr = ver.pull_request_version_id %>
211 211 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
212 212
213 213 <tr class="version-pr" style="display: ${display_row}">
214 214 <td>
215 215 <code>
216 216 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
217 217 </code>
218 218 </td>
219 219 <td>
220 220 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
221 221 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
222 222 </td>
223 223 <td>
224 224 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
225 225 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
226 226
227 227 </td>
228 228 <td>
229 229 % if c.at_version_num != ver_pr:
230 230 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
231 231 <code>
232 232 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
233 233 </code>
234 234 % endif
235 235 </td>
236 236 <td>
237 237 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
238 238 </td>
239 239 <td>
240 240 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
241 241 </td>
242 242 </tr>
243 243 % endfor
244 244
245 245 <tr>
246 246 <td colspan="6">
247 247 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
248 248 data-label-text-locked="${_('select versions to show changes')}"
249 249 data-label-text-diff="${_('show changes between versions')}"
250 250 data-label-text-show="${_('show pull request for this version')}"
251 251 >
252 252 ${_('select versions to show changes')}
253 253 </button>
254 254 </td>
255 255 </tr>
256 256 </table>
257 257 % else:
258 258 <div>
259 259 ${_('Pull request versions not available')}.
260 260 </div>
261 261 % endif
262 262 </div>
263 263 </div>
264 264
265 265 </div>
266 266
267 267 </div>
268 268
269 269
270 270 </div>
271 271
272 272 </div>
273 273
274 274 <div class="box">
275 275
276 276 % if c.state_progressing:
277 277
278 278 <h2 style="text-align: center">
279 279 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
280 280
281 281 % if c.is_super_admin:
282 282 <br/>
283 283 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
284 284 % endif
285 285 </h2>
286 286
287 287 % else:
288 288
289 289 ## Diffs rendered here
290 290 <div class="table" >
291 291 <div id="changeset_compare_view_content">
292 292 ##CS
293 293 % if c.missing_requirements:
294 294 <div class="box">
295 295 <div class="alert alert-warning">
296 296 <div>
297 297 <strong>${_('Missing requirements:')}</strong>
298 298 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
299 299 </div>
300 300 </div>
301 301 </div>
302 302 % elif c.missing_commits:
303 303 <div class="box">
304 304 <div class="alert alert-warning">
305 305 <div>
306 306 <strong>${_('Missing commits')}:</strong>
307 307 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
308 308 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
309 309 ${_('Consider doing a `force update commits` in case you think this is an error.')}
310 310 </div>
311 311 </div>
312 312 </div>
313 313 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
314 314 <div class="box">
315 315 <div class="alert alert-info">
316 316 <div>
317 317 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
318 318 </div>
319 319 </div>
320 320 </div>
321 321 % endif
322 322
323 323 <div class="compare_view_commits_title">
324 324 % if not c.compare_mode:
325 325
326 326 % if c.at_version_index:
327 327 <h4>
328 328 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
329 329 </h4>
330 330 % endif
331 331
332 332 <div class="pull-left">
333 333 <div class="btn-group">
334 334 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
335 335 % if c.collapse_all_commits:
336 336 <i class="icon-plus-squared-alt icon-no-margin"></i>
337 337 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
338 338 % else:
339 339 <i class="icon-minus-squared-alt icon-no-margin"></i>
340 340 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
341 341 % endif
342 342 </a>
343 343 </div>
344 344 </div>
345 345
346 346 <div class="pull-right">
347 347 % if c.allowed_to_update and not c.pull_request.is_closed():
348 348
349 349 <div class="btn-group btn-group-actions">
350 350 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
351 351 ${_('Update commits')}
352 352 </a>
353 353
354 354 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
355 355 <i class="icon-down"></i>
356 356 </a>
357 357
358 358 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
359 359 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
360 360 <li>
361 361 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
362 362 ${_('Force update commits')}
363 363 </a>
364 364 <div class="action-help-block">
365 365 ${_('Update commits and force refresh this pull request.')}
366 366 </div>
367 367 </li>
368 368 </ul>
369 369 </div>
370 370 </div>
371 371
372 372 % else:
373 373 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
374 374 % endif
375 375
376 376 </div>
377 377 % endif
378 378 </div>
379 379
380 380 % if not c.missing_commits:
381 381 ## COMPARE RANGE DIFF MODE
382 382 % if c.compare_mode:
383 383 % if c.at_version:
384 384 <h4>
385 385 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
386 386 </h4>
387 387
388 388 <div class="subtitle-compare">
389 389 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
390 390 </div>
391 391
392 392 <div class="container">
393 393 <table class="rctable compare_view_commits">
394 394 <tr>
395 395 <th></th>
396 396 <th>${_('Time')}</th>
397 397 <th>${_('Author')}</th>
398 398 <th>${_('Commit')}</th>
399 399 <th></th>
400 400 <th>${_('Description')}</th>
401 401 </tr>
402 402
403 403 % for c_type, commit in c.commit_changes:
404 404 % if c_type in ['a', 'r']:
405 405 <%
406 406 if c_type == 'a':
407 407 cc_title = _('Commit added in displayed changes')
408 408 elif c_type == 'r':
409 409 cc_title = _('Commit removed in displayed changes')
410 410 else:
411 411 cc_title = ''
412 412 %>
413 413 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
414 414 <td>
415 415 <div class="commit-change-indicator color-${c_type}-border">
416 416 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
417 417 ${c_type.upper()}
418 418 </div>
419 419 </div>
420 420 </td>
421 421 <td class="td-time">
422 422 ${h.age_component(commit.date)}
423 423 </td>
424 424 <td class="td-user">
425 425 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
426 426 </td>
427 427 <td class="td-hash">
428 428 <code>
429 429 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
430 430 r${commit.idx}:${h.short_id(commit.raw_id)}
431 431 </a>
432 432 ${h.hidden('revisions', commit.raw_id)}
433 433 </code>
434 434 </td>
435 435 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
436 436 <i class="icon-expand-linked"></i>
437 437 </td>
438 438 <td class="mid td-description">
439 439 <div class="log-container truncate-wrap">
440 440 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
441 441 </div>
442 442 </td>
443 443 </tr>
444 444 % endif
445 445 % endfor
446 446 </table>
447 447 </div>
448 448
449 449 % endif
450 450
451 451 ## Regular DIFF
452 452 % else:
453 453 <%include file="/compare/compare_commits.mako" />
454 454 % endif
455 455
456 456 <div class="cs_files">
457 457 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
458 458
459 459 <%
460 460 pr_menu_data = {
461 461 'outdated_comm_count_ver': outdated_comm_count_ver,
462 462 'pull_request': c.pull_request
463 463 }
464 464 %>
465 465
466 466 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
467 467
468 468 % if c.range_diff_on:
469 469 % for commit in c.commit_ranges:
470 470 ${cbdiffs.render_diffset(
471 471 c.changes[commit.raw_id],
472 472 commit=commit, use_comments=True,
473 473 collapse_when_files_over=5,
474 474 disable_new_comments=True,
475 475 deleted_files_comments=c.deleted_files_comments,
476 476 inline_comments=c.inline_comments,
477 477 pull_request_menu=pr_menu_data, show_todos=False)}
478 478 % endfor
479 479 % else:
480 480 ${cbdiffs.render_diffset(
481 481 c.diffset, use_comments=True,
482 482 collapse_when_files_over=30,
483 483 disable_new_comments=not c.allowed_to_comment,
484 484 deleted_files_comments=c.deleted_files_comments,
485 485 inline_comments=c.inline_comments,
486 486 pull_request_menu=pr_menu_data, show_todos=False)}
487 487 % endif
488 488
489 489 </div>
490 490 % else:
491 491 ## skipping commits we need to clear the view for missing commits
492 492 <div style="clear:both;"></div>
493 493 % endif
494 494
495 495 </div>
496 496 </div>
497 497
498 498 ## template for inline comment form
499 499 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
500 500
501 501 ## comments heading with count
502 502 <div class="comments-heading">
503 503 <i class="icon-comment"></i>
504 504 ${_('General Comments')} ${len(c.comments)}
505 505 </div>
506 506
507 507 ## render general comments
508 508 <div id="comment-tr-show">
509 509 % if general_outdated_comm_count_ver:
510 510 <div class="info-box">
511 511 % if general_outdated_comm_count_ver == 1:
512 512 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
513 513 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
514 514 % else:
515 515 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
516 516 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
517 517 % endif
518 518 </div>
519 519 % endif
520 520 </div>
521 521
522 522 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
523 523
524 524 % if not c.pull_request.is_closed():
525 525 ## main comment form and it status
526 526 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
527 527 pull_request_id=c.pull_request.pull_request_id),
528 528 c.pull_request_review_status,
529 529 is_pull_request=True, change_status=c.allowed_to_change_status)}
530 530
531 531 ## merge status, and merge action
532 532 <div class="pull-request-merge">
533 533 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
534 534 </div>
535 535
536 536 %endif
537 537
538 538 % endif
539 539 </div>
540 540
541 541
542 542 ### NAV SIDEBAR
543 543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
544 544 <div class="sidenav navbar__inner" >
545 545 ## TOGGLE
546 546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
547 547 <a href="#toggleSidebar" class="grey-link-action">
548 548
549 549 </a>
550 550 </div>
551 551
552 552 ## CONTENT
553 553 <div class="sidebar-content">
554 554
555 555 ## RULES SUMMARY/RULES
556 556 <div class="sidebar-element clear-both">
557 557 <% vote_title = _ungettext(
558 558 'Status calculated based on votes from {} reviewer',
559 559 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
560 560 %>
561 561
562 562 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
563 563 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
564 564 ${c.reviewers_count}
565 565 </div>
566 566
567 567 ## REVIEW RULES
568 568 <div id="review_rules" style="display: none" class="">
569 569 <div class="right-sidebar-expanded-state pr-details-title">
570 570 <span class="sidebar-heading">
571 571 ${_('Reviewer rules')}
572 572 </span>
573 573
574 574 </div>
575 575 <div class="pr-reviewer-rules">
576 576 ## review rules will be appended here, by default reviewers logic
577 577 </div>
578 578 <input id="review_data" type="hidden" name="review_data" value="">
579 579 </div>
580 580
581 581 ## REVIEWERS
582 582 <div class="right-sidebar-expanded-state pr-details-title">
583 583 <span class="tooltip sidebar-heading" title="${vote_title}">
584 584 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
585 585 ${_('Reviewers')}
586 586 </span>
587 587 %if c.allowed_to_update:
588 588 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
589 589 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
590 590 %else:
591 591 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
592 592 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
593 593 %endif
594 594 </div>
595 595
596 596 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
597 597
598 598 ## members redering block
599 599 <input type="hidden" name="__start__" value="review_members:sequence">
600 600
601 601 <table id="review_members" class="group_members">
602 602 ## This content is loaded via JS and ReviewersPanel
603 603 </table>
604 604
605 605 <input type="hidden" name="__end__" value="review_members:sequence">
606 606 ## end members redering block
607 607
608 608 %if not c.pull_request.is_closed():
609 609 <div id="add_reviewer" class="ac" style="display: none;">
610 610 %if c.allowed_to_update:
611 611 % if not c.forbid_adding_reviewers:
612 612 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
613 613 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
614 614 <div id="reviewers_container"></div>
615 615 </div>
616 616 % endif
617 617 <div class="pull-right" style="margin-bottom: 15px">
618 618 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
619 619 </div>
620 620 %endif
621 621 </div>
622 622 %endif
623 623 </div>
624 624 </div>
625 625
626 626 ## OBSERVERS
627 % if c.rhodecode_edition_id == 'EE':
627 628 <div class="sidebar-element clear-both">
628 629 <% vote_title = _ungettext(
629 630 '{} observer without voting right.',
630 631 '{} observers without voting right.', c.observers_count).format(c.observers_count)
631 632 %>
632 633
633 634 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
634 635 <i class="icon-circle-thin"></i>
635 636 ${c.observers_count}
636 637 </div>
637 638
638 639 <div class="right-sidebar-expanded-state pr-details-title">
639 640 <span class="tooltip sidebar-heading" title="${vote_title}">
640 641 <i class="icon-circle-thin"></i>
641 642 ${_('Observers')}
642 643 </span>
643 644 %if c.allowed_to_update:
644 645 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
645 646 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
646 647 %endif
647 648 </div>
648 649
649 650 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
650 651 ## members redering block
651 652 <input type="hidden" name="__start__" value="observer_members:sequence">
652 653
653 654 <table id="observer_members" class="group_members">
654 655 ## This content is loaded via JS and ReviewersPanel
655 656 </table>
656 657
657 658 <input type="hidden" name="__end__" value="observer_members:sequence">
658 659 ## end members redering block
659 660
660 661 %if not c.pull_request.is_closed():
661 662 <div id="add_observer" class="ac" style="display: none;">
662 663 %if c.allowed_to_update:
663 664 % if not c.forbid_adding_reviewers or 1:
664 665 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
665 666 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
666 667 <div id="observers_container"></div>
667 668 </div>
668 669 % endif
669 670 <div class="pull-right" style="margin-bottom: 15px">
670 671 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
671 672 </div>
672 673 %endif
673 674 </div>
674 675 %endif
675 676 </div>
676 677 </div>
678 % endif
677 679
678 680 ## TODOs
679 681 <div class="sidebar-element clear-both">
680 682 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
681 683 <i class="icon-flag-filled"></i>
682 684 <span id="todos-count">${len(c.unresolved_comments)}</span>
683 685 </div>
684 686
685 687 <div class="right-sidebar-expanded-state pr-details-title">
686 688 ## Only show unresolved, that is only what matters
687 689 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
688 690 <i class="icon-flag-filled"></i>
689 691 TODOs
690 692 </span>
691 693
692 694 % if not c.at_version:
693 695 % if c.resolved_comments:
694 696 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
695 697 % else:
696 698 <span class="block-right last-item noselect">Show resolved</span>
697 699 % endif
698 700 % endif
699 701 </div>
700 702
701 703 <div class="right-sidebar-expanded-state pr-details-content">
702 704
703 705 % if c.at_version:
704 706 <table>
705 707 <tr>
706 708 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
707 709 </tr>
708 710 </table>
709 711 % else:
710 712 % if c.unresolved_comments + c.resolved_comments:
711 713 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
712 714 % else:
713 715 <table>
714 716 <tr>
715 717 <td>
716 718 ${_('No TODOs yet')}
717 719 </td>
718 720 </tr>
719 721 </table>
720 722 % endif
721 723 % endif
722 724 </div>
723 725 </div>
724 726
725 727 ## COMMENTS
726 728 <div class="sidebar-element clear-both">
727 729 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
728 730 <i class="icon-comment" style="color: #949494"></i>
729 731 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
730 732 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
731 733 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
732 734 </div>
733 735
734 736 <div class="right-sidebar-expanded-state pr-details-title">
735 737 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
736 738 <i class="icon-comment" style="color: #949494"></i>
737 739 ${_('Comments')}
738 740
739 741 ## % if outdated_comm_count_ver:
740 742 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
741 743 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
742 744 ## </a>
743 745 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
744 746 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
745 747
746 748 ## % else:
747 749 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
748 750 ## % endif
749 751
750 752 </span>
751 753
752 754 % if outdated_comm_count_ver:
753 755 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
754 756 % else:
755 757 <span class="block-right last-item noselect">Show hidden</span>
756 758 % endif
757 759
758 760 </div>
759 761
760 762 <div class="right-sidebar-expanded-state pr-details-content">
761 763 % if c.inline_comments_flat + c.comments:
762 764 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
763 765 % else:
764 766 <table>
765 767 <tr>
766 768 <td>
767 769 ${_('No Comments yet')}
768 770 </td>
769 771 </tr>
770 772 </table>
771 773 % endif
772 774 </div>
773 775
774 776 </div>
775 777
776 778 ## Referenced Tickets
777 779 <div class="sidebar-element clear-both">
778 780 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
779 781 <i class="icon-info-circled"></i>
780 782 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
781 783 </div>
782 784
783 785 <div class="right-sidebar-expanded-state pr-details-title">
784 786 <span class="sidebar-heading">
785 787 <i class="icon-info-circled"></i>
786 788 ${_('Referenced Tickets')}
787 789 </span>
788 790 </div>
789 791 <div class="right-sidebar-expanded-state pr-details-content">
790 792 <table>
791 793
792 794 <tr><td><code>${_('In pull request description')}:</code></td></tr>
793 795 % if c.referenced_desc_issues:
794 796 % for ticket_dict in sorted(c.referenced_desc_issues):
795 797 <tr>
796 798 <td>
797 799 <a href="${ticket_dict.get('url')}">
798 800 ${ticket_dict.get('id')}
799 801 </a>
800 802 </td>
801 803 </tr>
802 804 % endfor
803 805 % else:
804 806 <tr>
805 807 <td>
806 808 ${_('No Ticket data found.')}
807 809 </td>
808 810 </tr>
809 811 % endif
810 812
811 813 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
812 814 % if c.referenced_commit_issues:
813 815 % for ticket_dict in sorted(c.referenced_commit_issues):
814 816 <tr>
815 817 <td>
816 818 <a href="${ticket_dict.get('url')}">
817 819 ${ticket_dict.get('id')}
818 820 </a>
819 821 </td>
820 822 </tr>
821 823 % endfor
822 824 % else:
823 825 <tr>
824 826 <td>
825 827 ${_('No Ticket data found.')}
826 828 </td>
827 829 </tr>
828 830 % endif
829 831 </table>
830 832
831 833 </div>
832 834 </div>
833 835
834 836 </div>
835 837
836 838 </div>
837 839 </aside>
838 840
839 841 ## This JS needs to be at the end
840 842 <script type="text/javascript">
841 843
842 844 versionController = new VersionController();
843 845 versionController.init();
844 846
845 847 reviewersController = new ReviewersController();
846 848 commitsController = new CommitsController();
847 849
848 850 updateController = new UpdatePrController();
849 851
850 852 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
851 853 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
852 854 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
853 855
854 856 (function () {
855 857 "use strict";
856 858
857 859 // custom code mirror
858 860 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
859 861
860 862 PRDetails.init();
861 863 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
862 864 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
863 865
864 866 window.showOutdated = function (self) {
865 867 $('.comment-inline.comment-outdated').show();
866 868 $('.filediff-outdated').show();
867 869 $('.showOutdatedComments').hide();
868 870 $('.hideOutdatedComments').show();
869 871 };
870 872
871 873 window.hideOutdated = function (self) {
872 874 $('.comment-inline.comment-outdated').hide();
873 875 $('.filediff-outdated').hide();
874 876 $('.hideOutdatedComments').hide();
875 877 $('.showOutdatedComments').show();
876 878 };
877 879
878 880 window.refreshMergeChecks = function () {
879 881 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
880 882 $('.pull-request-merge').css('opacity', 0.3);
881 883 $('.action-buttons-extra').css('opacity', 0.3);
882 884
883 885 $('.pull-request-merge').load(
884 886 loadUrl, function () {
885 887 $('.pull-request-merge').css('opacity', 1);
886 888
887 889 $('.action-buttons-extra').css('opacity', 1);
888 890 }
889 891 );
890 892 };
891 893
892 894 window.closePullRequest = function (status) {
893 895 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
894 896 return false;
895 897 }
896 898 // inject closing flag
897 899 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
898 900 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
899 901 $(generalCommentForm.submitForm).submit();
900 902 };
901 903
902 904 //TODO this functionality is now missing
903 905 $('#show-outdated-comments').on('click', function (e) {
904 906 var button = $(this);
905 907 var outdated = $('.comment-outdated');
906 908
907 909 if (button.html() === "(Show)") {
908 910 button.html("(Hide)");
909 911 outdated.show();
910 912 } else {
911 913 button.html("(Show)");
912 914 outdated.hide();
913 915 }
914 916 });
915 917
916 918 $('#merge_pull_request_form').submit(function () {
917 919 if (!$('#merge_pull_request').attr('disabled')) {
918 920 $('#merge_pull_request').attr('disabled', 'disabled');
919 921 }
920 922 return true;
921 923 });
922 924
923 925 $('#edit_pull_request').on('click', function (e) {
924 926 var title = $('#pr-title-input').val();
925 927 var description = codeMirrorInstance.getValue();
926 928 var renderer = $('#pr-renderer-input').val();
927 929 editPullRequest(
928 930 "${c.repo_name}", "${c.pull_request.pull_request_id}",
929 931 title, description, renderer);
930 932 });
931 933
932 934 var $updateButtons = $('#update_reviewers,#update_observers');
933 935 $updateButtons.on('click', function (e) {
934 936 var role = $(this).data('role');
935 937 $updateButtons.attr('disabled', 'disabled');
936 938 $updateButtons.addClass('disabled');
937 939 $updateButtons.html(_gettext('Saving...'));
938 940 reviewersController.updateReviewers(
939 941 templateContext.repo_name,
940 942 templateContext.pull_request_data.pull_request_id,
941 943 role
942 944 );
943 945 });
944 946
945 947 // fixing issue with caches on firefox
946 948 $('#update_commits').removeAttr("disabled");
947 949
948 950 $('.show-inline-comments').on('click', function (e) {
949 951 var boxid = $(this).attr('data-comment-id');
950 952 var button = $(this);
951 953
952 954 if (button.hasClass("comments-visible")) {
953 955 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
954 956 $(this).hide();
955 957 });
956 958 button.removeClass("comments-visible");
957 959 } else {
958 960 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
959 961 $(this).show();
960 962 });
961 963 button.addClass("comments-visible");
962 964 }
963 965 });
964 966
965 967 $('.show-inline-comments').on('change', function (e) {
966 968 var show = 'none';
967 969 var target = e.currentTarget;
968 970 if (target.checked) {
969 971 show = ''
970 972 }
971 973 var boxid = $(target).attr('id_for');
972 974 var comments = $('#{0} .inline-comments'.format(boxid));
973 975 var fn_display = function (idx) {
974 976 $(this).css('display', show);
975 977 };
976 978 $(comments).each(fn_display);
977 979 var btns = $('#{0} .inline-comments-button'.format(boxid));
978 980 $(btns).each(fn_display);
979 981 });
980 982
981 983 // register submit callback on commentForm form to track TODOs
982 984 window.commentFormGlobalSubmitSuccessCallback = function () {
983 985 refreshMergeChecks();
984 986 };
985 987
986 988 ReviewerAutoComplete('#user', reviewersController);
987 989 ObserverAutoComplete('#observer', reviewersController);
988 990
989 991 })();
990 992
991 993 $(document).ready(function () {
992 994
993 995 var channel = '${c.pr_broadcast_channel}';
994 996 new ReviewerPresenceController(channel)
995 997
996 998 })
997 999 </script>
998 1000
999 1001 </%def>
General Comments 0
You need to be logged in to leave comments. Login now